Skip to content

Commit 4989e14

Browse files
Merge pull request #2417 from stefannikolei/stefannikolei/arm/colorconverter_ycbcrarm
Port colorconverter YCbCr and YCCk to arm
2 parents 3d40c91 + ba5ae6e commit 4989e14

File tree

6 files changed

+319
-4
lines changed

6 files changed

+319
-4
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.Intrinsics;
7+
using System.Runtime.Intrinsics.Arm;
8+
using System.Runtime.Intrinsics.X86;
9+
using static SixLabors.ImageSharp.SimdUtils;
10+
11+
// ReSharper disable ImpureMethodCallOnReadonlyValueField
12+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
13+
14+
internal abstract partial class JpegColorConverterBase
15+
{
16+
internal sealed class YCbCrArm : JpegColorConverterArm
17+
{
18+
public YCbCrArm(int precision)
19+
: base(JpegColorSpace.YCbCr, precision)
20+
{
21+
}
22+
23+
/// <inheritdoc/>
24+
public override void ConvertToRgbInplace(in ComponentValues values)
25+
{
26+
ref Vector128<float> c0Base =
27+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
28+
ref Vector128<float> c1Base =
29+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
30+
ref Vector128<float> c2Base =
31+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
32+
33+
// Used for the color conversion
34+
var chromaOffset = Vector128.Create(-this.HalfValue);
35+
var scale = Vector128.Create(1 / this.MaximumValue);
36+
var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
37+
var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
38+
var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
39+
var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
40+
41+
// Walking 8 elements at one step:
42+
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
43+
for (nuint i = 0; i < n; i++)
44+
{
45+
// y = yVals[i];
46+
// cb = cbVals[i] - 128F;
47+
// cr = crVals[i] - 128F;
48+
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
49+
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
50+
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
51+
52+
Vector128<float> y = c0;
53+
Vector128<float> cb = AdvSimd.Add(c1, chromaOffset);
54+
Vector128<float> cr = AdvSimd.Add(c2, chromaOffset);
55+
56+
// r = y + (1.402F * cr);
57+
// g = y - (0.344136F * cb) - (0.714136F * cr);
58+
// b = y + (1.772F * cb);
59+
Vector128<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
60+
Vector128<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
61+
Vector128<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
62+
63+
r = AdvSimd.Multiply(AdvSimd.RoundToNearest(r), scale);
64+
g = AdvSimd.Multiply(AdvSimd.RoundToNearest(g), scale);
65+
b = AdvSimd.Multiply(AdvSimd.RoundToNearest(b), scale);
66+
67+
c0 = r;
68+
c1 = g;
69+
c2 = b;
70+
}
71+
}
72+
73+
/// <inheritdoc/>
74+
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
75+
{
76+
ref Vector128<float> destY =
77+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
78+
ref Vector128<float> destCb =
79+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
80+
ref Vector128<float> destCr =
81+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
82+
83+
ref Vector128<float> srcR =
84+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
85+
ref Vector128<float> srcG =
86+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
87+
ref Vector128<float> srcB =
88+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
89+
90+
// Used for the color conversion
91+
var chromaOffset = Vector128.Create(this.HalfValue);
92+
93+
var f0299 = Vector128.Create(0.299f);
94+
var f0587 = Vector128.Create(0.587f);
95+
var f0114 = Vector128.Create(0.114f);
96+
var fn0168736 = Vector128.Create(-0.168736f);
97+
var fn0331264 = Vector128.Create(-0.331264f);
98+
var fn0418688 = Vector128.Create(-0.418688f);
99+
var fn0081312F = Vector128.Create(-0.081312F);
100+
var f05 = Vector128.Create(0.5f);
101+
102+
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
103+
for (nuint i = 0; i < n; i++)
104+
{
105+
Vector128<float> r = Unsafe.Add(ref srcR, i);
106+
Vector128<float> g = Unsafe.Add(ref srcG, i);
107+
Vector128<float> b = Unsafe.Add(ref srcB, i);
108+
109+
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
110+
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
111+
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
112+
Vector128<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
113+
Vector128<float> cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
114+
Vector128<float> cr = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(fn0081312F, b), fn0418688, g), f05, r));
115+
116+
Unsafe.Add(ref destY, i) = y;
117+
Unsafe.Add(ref destCb, i) = cb;
118+
Unsafe.Add(ref destCr, i) = cr;
119+
}
120+
}
121+
}
122+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.Intrinsics;
7+
using System.Runtime.Intrinsics.Arm;
8+
using System.Runtime.Intrinsics.X86;
9+
using static SixLabors.ImageSharp.SimdUtils;
10+
11+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
12+
13+
internal abstract partial class JpegColorConverterBase
14+
{
15+
internal sealed class YccKArm64 : JpegColorConverterArm64
16+
{
17+
public YccKArm64(int precision)
18+
: base(JpegColorSpace.Ycck, precision)
19+
{
20+
}
21+
22+
/// <inheritdoc/>
23+
public override void ConvertToRgbInplace(in ComponentValues values)
24+
{
25+
ref Vector128<float> c0Base =
26+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
27+
ref Vector128<float> c1Base =
28+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
29+
ref Vector128<float> c2Base =
30+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
31+
ref Vector128<float> kBase =
32+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
33+
34+
// Used for the color conversion
35+
var chromaOffset = Vector128.Create(-this.HalfValue);
36+
var scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
37+
var max = Vector128.Create(this.MaximumValue);
38+
var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
39+
var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
40+
var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
41+
var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
42+
43+
// Walking 8 elements at one step:
44+
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
45+
for (nuint i = 0; i < n; i++)
46+
{
47+
// y = yVals[i];
48+
// cb = cbVals[i] - 128F;
49+
// cr = crVals[i] - 128F;
50+
// k = kVals[i] / 256F;
51+
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
52+
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
53+
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
54+
Vector128<float> y = c0;
55+
Vector128<float> cb = AdvSimd.Add(c1, chromaOffset);
56+
Vector128<float> cr = AdvSimd.Add(c2, chromaOffset);
57+
Vector128<float> scaledK = AdvSimd.Multiply(Unsafe.Add(ref kBase, i), scale);
58+
59+
// r = y + (1.402F * cr);
60+
// g = y - (0.344136F * cb) - (0.714136F * cr);
61+
// b = y + (1.772F * cb);
62+
Vector128<float> r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
63+
Vector128<float> g =
64+
HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
65+
Vector128<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
66+
67+
r = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(r));
68+
g = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(g));
69+
b = AdvSimd.Subtract(max, AdvSimd.RoundToNearest(b));
70+
71+
r = AdvSimd.Multiply(r, scaledK);
72+
g = AdvSimd.Multiply(g, scaledK);
73+
b = AdvSimd.Multiply(b, scaledK);
74+
75+
c0 = r;
76+
c1 = g;
77+
c2 = b;
78+
}
79+
}
80+
81+
/// <inheritdoc/>
82+
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
83+
{
84+
// rgb -> cmyk
85+
CmykArm64.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
86+
87+
// cmyk -> ycck
88+
ref Vector128<float> destY =
89+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
90+
ref Vector128<float> destCb =
91+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
92+
ref Vector128<float> destCr =
93+
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
94+
95+
ref Vector128<float> srcR = ref destY;
96+
ref Vector128<float> srcG = ref destCb;
97+
ref Vector128<float> srcB = ref destCr;
98+
99+
// Used for the color conversion
100+
var maxSampleValue = Vector128.Create(this.MaximumValue);
101+
102+
var chromaOffset = Vector128.Create(this.HalfValue);
103+
104+
var f0299 = Vector128.Create(0.299f);
105+
var f0587 = Vector128.Create(0.587f);
106+
var f0114 = Vector128.Create(0.114f);
107+
var fn0168736 = Vector128.Create(-0.168736f);
108+
var fn0331264 = Vector128.Create(-0.331264f);
109+
var fn0418688 = Vector128.Create(-0.418688f);
110+
var fn0081312F = Vector128.Create(-0.081312F);
111+
var f05 = Vector128.Create(0.5f);
112+
113+
nuint n = (uint)values.Component0.Length / (uint)Vector128<float>.Count;
114+
for (nuint i = 0; i < n; i++)
115+
{
116+
Vector128<float> r = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i));
117+
Vector128<float> g = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i));
118+
Vector128<float> b = AdvSimd.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i));
119+
120+
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
121+
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
122+
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
123+
Vector128<float> y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
124+
Vector128<float> cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
125+
Vector128<float> cr = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(fn0081312F, b), fn0418688, g), f05, r));
126+
127+
Unsafe.Add(ref destY, i) = y;
128+
Unsafe.Add(ref destCb, i) = cb;
129+
Unsafe.Add(ref destCr, i) = cr;
130+
}
131+
}
132+
}
133+
}

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ private static JpegColorConverterBase GetYCbCrConverter(int precision)
138138
return new YCbCrAvx(precision);
139139
}
140140

141+
if (JpegColorConverterArm.IsSupported)
142+
{
143+
return new YCbCrArm(precision);
144+
}
145+
141146
if (JpegColorConverterVector.IsSupported)
142147
{
143148
return new YCbCrVector(precision);
@@ -157,6 +162,11 @@ private static JpegColorConverterBase GetYccKConverter(int precision)
157162
return new YccKAvx(precision);
158163
}
159164

165+
if (JpegColorConverterArm64.IsSupported)
166+
{
167+
return new YccKArm64(precision);
168+
}
169+
160170
if (JpegColorConverterVector.IsSupported)
161171
{
162172
return new YccKVector(precision);

tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,12 @@ public void SimdVectorAvx()
3737

3838
new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values);
3939
}
40+
41+
[Benchmark]
42+
public void SimdVectorArm()
43+
{
44+
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
45+
46+
new JpegColorConverterBase.YCbCrArm(8).ConvertToRgbInplace(values);
47+
}
4048
}

tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,12 @@ public void SimdVectorAvx2()
3737

3838
new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values);
3939
}
40+
41+
[Benchmark]
42+
public void SimdVectorArm64()
43+
{
44+
var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
45+
46+
new JpegColorConverterBase.YccKArm64(8).ConvertToRgbInplace(values);
47+
}
4048
}

tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ static void RunTest(string arg)
156156
{
157157
expectedType = typeof(JpegColorConverterBase.CmykVector);
158158
}
159-
else if (AdvSimd.IsSupported)
159+
else if (AdvSimd.Arm64.IsSupported)
160160
{
161161
expectedType = typeof(JpegColorConverterBase.CmykArm64);
162162
}
@@ -191,7 +191,7 @@ static void RunTest(string arg)
191191
}
192192
else if (AdvSimd.IsSupported)
193193
{
194-
expectedType = typeof(JpegColorConverterBase.YCbCrVector);
194+
expectedType = typeof(JpegColorConverterBase.YCbCrArm);
195195
}
196196

197197
// act
@@ -222,9 +222,9 @@ static void RunTest(string arg)
222222
{
223223
expectedType = typeof(JpegColorConverterBase.YccKVector);
224224
}
225-
else if (AdvSimd.IsSupported)
225+
else if (AdvSimd.Arm64.IsSupported)
226226
{
227-
expectedType = typeof(JpegColorConverterBase.YccKVector);
227+
expectedType = typeof(JpegColorConverterBase.YccKArm64);
228228
}
229229

230230
// act
@@ -425,6 +425,23 @@ public void FromRgbToYCbCrAvx2(int seed) =>
425425
new JpegColorConverterBase.YCbCrScalar(8),
426426
precísion: 2);
427427

428+
[Theory]
429+
[MemberData(nameof(Seeds))]
430+
public void FromYCbCrArm(int seed) =>
431+
this.TestConversionToRgb(new JpegColorConverterBase.YCbCrArm(8),
432+
3,
433+
seed,
434+
new JpegColorConverterBase.YCbCrScalar(8));
435+
436+
[Theory]
437+
[MemberData(nameof(Seeds))]
438+
public void FromRgbToYCbCrArm(int seed) =>
439+
this.TestConversionFromRgb(new JpegColorConverterBase.YCbCrArm(8),
440+
3,
441+
seed,
442+
new JpegColorConverterBase.YCbCrScalar(8),
443+
precísion: 2);
444+
428445
[Theory]
429446
[MemberData(nameof(Seeds))]
430447
public void FromCmykAvx2(int seed) =>
@@ -536,6 +553,23 @@ public void FromRgbToYccKAvx2(int seed) =>
536553
new JpegColorConverterBase.YccKScalar(8),
537554
precísion: 4);
538555

556+
[Theory]
557+
[MemberData(nameof(Seeds))]
558+
public void FromYccKArm64(int seed) =>
559+
this.TestConversionToRgb(new JpegColorConverterBase.YccKArm64(8),
560+
4,
561+
seed,
562+
new JpegColorConverterBase.YccKScalar(8));
563+
564+
[Theory]
565+
[MemberData(nameof(Seeds))]
566+
public void FromRgbToYccKArm64(int seed) =>
567+
this.TestConversionFromRgb(new JpegColorConverterBase.YccKArm64(8),
568+
4,
569+
seed,
570+
new JpegColorConverterBase.YccKScalar(8),
571+
precísion: 4);
572+
539573
private void TestConversionToRgb(
540574
JpegColorConverterBase converter,
541575
int componentCount,

0 commit comments

Comments
 (0)