Skip to content

Commit 13897ae

Browse files
Merge pull request #2120 from br3aker/dp/jpeg-encoder-color-conversion
Jpeg encoder complete rewrite
2 parents 4a0a5cf + 5d4fa11 commit 13897ae

105 files changed

Lines changed: 3763 additions & 4496 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public static class HwIntrinsics
2121

2222
public static ReadOnlySpan<byte> PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 };
2323

24+
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 };
25+
26+
internal static ReadOnlySpan<byte> ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF };
27+
2428
private static ReadOnlySpan<byte> ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 };
2529

2630
private static ReadOnlySpan<byte> ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 };
@@ -962,6 +966,49 @@ internal static void PackFromRgbPlanesAvx2Reduce(
962966
blueChannel = blueChannel.Slice(slice);
963967
destination = destination.Slice(slice);
964968
}
969+
970+
internal static void UnpackToRgbPlanesAvx2Reduce(
971+
ref Span<float> redChannel,
972+
ref Span<float> greenChannel,
973+
ref Span<float> blueChannel,
974+
ref ReadOnlySpan<Rgb24> source)
975+
{
976+
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(source));
977+
ref Vector256<float> destRRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(redChannel));
978+
ref Vector256<float> destGRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(greenChannel));
979+
ref Vector256<float> destBRef = ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(blueChannel));
980+
981+
Vector256<uint> extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
982+
Vector256<byte> extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
983+
Vector256<byte> rgb, rg, bx;
984+
Vector256<float> r, g, b;
985+
986+
const int bytesPerRgbStride = 24;
987+
int count = (int)((uint)source.Length / 8);
988+
for (int i = 0; i < count; i++)
989+
{
990+
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
991+
992+
rgb = Avx2.Shuffle(rgb, extractRgbMask);
993+
994+
rg = Avx2.UnpackLow(rgb, Vector256<byte>.Zero);
995+
bx = Avx2.UnpackHigh(rgb, Vector256<byte>.Zero);
996+
997+
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256<byte>.Zero).AsInt32());
998+
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256<byte>.Zero).AsInt32());
999+
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256<byte>.Zero).AsInt32());
1000+
1001+
Unsafe.Add(ref destRRef, i) = r;
1002+
Unsafe.Add(ref destGRef, i) = g;
1003+
Unsafe.Add(ref destBRef, i) = b;
1004+
}
1005+
1006+
int sliceCount = count * 8;
1007+
redChannel = redChannel.Slice(sliceCount);
1008+
greenChannel = greenChannel.Slice(sliceCount);
1009+
blueChannel = blueChannel.Slice(sliceCount);
1010+
source = source.Slice(sliceCount);
1011+
}
9651012
}
9661013
}
9671014
}

src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ internal static void PackFromRgbPlanes(
6565
PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination);
6666
}
6767

68+
[MethodImpl(InliningOptions.ShortMethod)]
69+
internal static void UnpackToRgbPlanes(
70+
Span<float> redChannel,
71+
Span<float> greenChannel,
72+
Span<float> blueChannel,
73+
ReadOnlySpan<Rgb24> source)
74+
{
75+
DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!");
76+
DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
77+
DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!");
78+
79+
if (Avx2.IsSupported)
80+
{
81+
HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source);
82+
}
83+
84+
UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source);
85+
}
86+
6887
private static void PackFromRgbPlanesScalarBatchedReduce(
6988
ref ReadOnlySpan<byte> redChannel,
7089
ref ReadOnlySpan<byte> greenChannel,
@@ -200,5 +219,29 @@ private static void PackFromRgbPlanesRemainder(
200219
d.A = 255;
201220
}
202221
}
222+
223+
private static void UnpackToRgbPlanesScalar(
224+
Span<float> redChannel,
225+
Span<float> greenChannel,
226+
Span<float> blueChannel,
227+
ReadOnlySpan<Rgb24> source)
228+
{
229+
DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!");
230+
DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
231+
DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!");
232+
233+
ref float r = ref MemoryMarshal.GetReference(redChannel);
234+
ref float g = ref MemoryMarshal.GetReference(greenChannel);
235+
ref float b = ref MemoryMarshal.GetReference(blueChannel);
236+
ref Rgb24 rgb = ref MemoryMarshal.GetReference(source);
237+
238+
for (int i = 0; i < source.Length; i++)
239+
{
240+
ref Rgb24 src = ref Unsafe.Add(ref rgb, i);
241+
Unsafe.Add(ref r, i) = src.R;
242+
Unsafe.Add(ref g, i) = src.G;
243+
Unsafe.Add(ref b, i) = src.B;
244+
}
245+
}
203246
}
204247
}

src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ public void CopyTo(Span<int> destination)
122122
}
123123
}
124124

125+
public static Block8x8 Load(ReadOnlySpan<byte> data)
126+
{
127+
Unsafe.SkipInit(out Block8x8 result);
128+
result.LoadFrom(data);
129+
return result;
130+
}
131+
132+
public void LoadFrom(ReadOnlySpan<byte> source)
133+
{
134+
for (int i = 0; i < Size; i++)
135+
{
136+
this[i] = source[i];
137+
}
138+
}
139+
125140
/// <summary>
126141
/// Load raw 16bit integers from source.
127142
/// </summary>

src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// <auto-generated />
99
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
1010
{
11-
internal partial struct Block8x8F
11+
internal partial struct Block8x8F
1212
{
1313
/// <summary>
1414
/// Level shift by +maximum/2, clip to [0, maximum]
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
using System.Runtime.CompilerServices;
6+
7+
// ReSharper disable UseObjectOrCollectionInitializer
8+
// ReSharper disable InconsistentNaming
9+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
10+
{
11+
internal partial struct Block8x8F
12+
{
13+
[MethodImpl(InliningOptions.ShortMethod)]
14+
public void ScaledCopyFrom(ref float areaOrigin, int areaStride) =>
15+
CopyFrom1x1Scale(ref Unsafe.As<float, byte>(ref areaOrigin), ref Unsafe.As<Block8x8F, byte>(ref this), areaStride);
16+
17+
[MethodImpl(InliningOptions.ShortMethod)]
18+
public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
19+
{
20+
if (horizontalScale == 1 && verticalScale == 1)
21+
{
22+
CopyTo1x1Scale(ref Unsafe.As<Block8x8F, byte>(ref this), ref Unsafe.As<float, byte>(ref areaOrigin), areaStride);
23+
return;
24+
}
25+
26+
if (horizontalScale == 2 && verticalScale == 2)
27+
{
28+
this.CopyTo2x2Scale(ref areaOrigin, areaStride);
29+
return;
30+
}
31+
32+
// TODO: Optimize: implement all cases with scale-specific, loopless code!
33+
this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
34+
}
35+
36+
private void CopyTo2x2Scale(ref float areaOrigin, int areaStride)
37+
{
38+
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref areaOrigin);
39+
int destStride = (int)((uint)areaStride / 2);
40+
41+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride);
42+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride);
43+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride);
44+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride);
45+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride);
46+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride);
47+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride);
48+
WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride);
49+
50+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
51+
static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride)
52+
{
53+
ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row);
54+
ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1);
55+
56+
nint offset = 2 * row * destStride;
57+
ref Vector4 dTopLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset));
58+
ref Vector4 dBottomLeft = ref Unsafe.As<Vector2, Vector4>(ref Unsafe.Add(ref destBase, offset + destStride));
59+
60+
var xyLeft = new Vector4(sLeft.X);
61+
xyLeft.Z = sLeft.Y;
62+
xyLeft.W = sLeft.Y;
63+
64+
var zwLeft = new Vector4(sLeft.Z);
65+
zwLeft.Z = sLeft.W;
66+
zwLeft.W = sLeft.W;
67+
68+
var xyRight = new Vector4(sRight.X);
69+
xyRight.Z = sRight.Y;
70+
xyRight.W = sRight.Y;
71+
72+
var zwRight = new Vector4(sRight.Z);
73+
zwRight.Z = sRight.W;
74+
zwRight.W = sRight.W;
75+
76+
dTopLeft = xyLeft;
77+
Unsafe.Add(ref dTopLeft, 1) = zwLeft;
78+
Unsafe.Add(ref dTopLeft, 2) = xyRight;
79+
Unsafe.Add(ref dTopLeft, 3) = zwRight;
80+
81+
dBottomLeft = xyLeft;
82+
Unsafe.Add(ref dBottomLeft, 1) = zwLeft;
83+
Unsafe.Add(ref dBottomLeft, 2) = xyRight;
84+
Unsafe.Add(ref dBottomLeft, 3) = zwRight;
85+
}
86+
}
87+
88+
[MethodImpl(InliningOptions.ColdPath)]
89+
private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
90+
{
91+
for (int y = 0; y < 8; y++)
92+
{
93+
int yy = y * verticalScale;
94+
int y8 = y * 8;
95+
96+
for (int x = 0; x < 8; x++)
97+
{
98+
int xx = x * horizontalScale;
99+
100+
float value = this[y8 + x];
101+
nint baseIdx = (yy * areaStride) + xx;
102+
103+
for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride)
104+
{
105+
for (nint j = 0; j < horizontalScale; j++)
106+
{
107+
// area[xx + j, yy + i] = value;
108+
Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
109+
}
110+
}
111+
}
112+
}
113+
}
114+
115+
private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride)
116+
{
117+
int destStride = areaStride * sizeof(float);
118+
119+
CopyRowImpl(ref origin, ref dest, destStride, 0);
120+
CopyRowImpl(ref origin, ref dest, destStride, 1);
121+
CopyRowImpl(ref origin, ref dest, destStride, 2);
122+
CopyRowImpl(ref origin, ref dest, destStride, 3);
123+
CopyRowImpl(ref origin, ref dest, destStride, 4);
124+
CopyRowImpl(ref origin, ref dest, destStride, 5);
125+
CopyRowImpl(ref origin, ref dest, destStride, 6);
126+
CopyRowImpl(ref origin, ref dest, destStride, 7);
127+
128+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
129+
static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row)
130+
{
131+
origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float));
132+
dest = ref Unsafe.Add(ref dest, row * destStride);
133+
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
134+
}
135+
}
136+
137+
private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride)
138+
{
139+
int destStride = areaStride * sizeof(float);
140+
141+
CopyRowImpl(ref origin, ref dest, destStride, 0);
142+
CopyRowImpl(ref origin, ref dest, destStride, 1);
143+
CopyRowImpl(ref origin, ref dest, destStride, 2);
144+
CopyRowImpl(ref origin, ref dest, destStride, 3);
145+
CopyRowImpl(ref origin, ref dest, destStride, 4);
146+
CopyRowImpl(ref origin, ref dest, destStride, 5);
147+
CopyRowImpl(ref origin, ref dest, destStride, 6);
148+
CopyRowImpl(ref origin, ref dest, destStride, 7);
149+
150+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
151+
static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row)
152+
{
153+
origin = ref Unsafe.Add(ref origin, row * sourceStride);
154+
dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float));
155+
Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float));
156+
}
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)