Skip to content

Commit 56b353a

Browse files
committed
初步完成蒙层处理逻辑
1 parent 4bd0dec commit 56b353a

File tree

7 files changed

+332
-2
lines changed

7 files changed

+332
-2
lines changed
Lines changed: 235 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,243 @@
1-
using System;
1+
using SixLabors.ImageSharp.PixelFormats;
2+
3+
using System;
24
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.IO.Pipelines;
37
using System.Linq;
48
using System.Text;
59
using System.Threading.Tasks;
610

711
namespace DotNetCampus.MediaConverters.Imaging.Effect;
8-
internal class SoftEdgeHelper
12+
13+
public class SoftEdgeHelper
914
{
15+
/// <summary>
16+
/// 创建柔化边缘蒙层
17+
/// </summary>
18+
/// <param name="bitmap"></param>
19+
/// <param name="radius"></param>
20+
/// <exception cref="ArgumentOutOfRangeException"></exception>
21+
public static void SetSoftEdgeMask(SixLabors.ImageSharp.Image<Rgba32> bitmap, float radius)
22+
{
23+
if (radius < 0) throw new ArgumentOutOfRangeException(nameof(radius), "Radius must greater than or equal to 0.");
24+
25+
var cols = bitmap.Width;
26+
var rows = bitmap.Height;
27+
28+
//var channels = 4;
29+
30+
var offsetX = (int) Math.Round(radius / 4.0);
31+
var offsetY = (int) Math.Round(radius / 4.0);
32+
33+
var source = new byte[cols, rows];
34+
// 创建Alpha通道蒙层Maps
35+
bitmap.ProcessPixelRows(pixelAccessor
36+
=>
37+
{
38+
for (var row = 0; row < rows; row++)
39+
{
40+
var pixelRow = pixelAccessor.GetRowSpan(row);
41+
for (var col = 0; col < cols; col++)
42+
{
43+
var pixel = pixelRow[col];
44+
source[col, row] = pixel.A == 0 ? (byte) 0 : byte.MaxValue;
45+
}
46+
}
47+
});
48+
49+
//腐蚀
50+
byte[/*cols*/, /*rows*/]? erodeMask = null;
51+
52+
for (var iteration = 0; iteration < 3; iteration++)
53+
{
54+
var target = new byte[cols, rows];
55+
var sourceCopy = source;
56+
Parallel.For(offsetY, rows - offsetY,
57+
row => { BatchAlphaErode(sourceCopy, target, row, cols, offsetX, offsetY); });
58+
59+
source = target;
60+
erodeMask = target;
61+
}
62+
63+
//平滑
64+
for (var iteration = 0; iteration < 5; iteration++)
65+
{
66+
var target = new byte[cols, rows];
67+
var sourceCopy = source;
68+
Parallel.For(0, rows,
69+
row => { BatchAlphaBlur(sourceCopy, target, row, rows, cols, offsetX, offsetY); });
70+
71+
source = target;
72+
erodeMask = target;
73+
}
74+
75+
if (erodeMask is null)
76+
{
77+
return;
78+
}
79+
80+
// ApplySoftEdgeAlphaMask
81+
bitmap.ProcessPixelRows(pixelAccessor
82+
=>
83+
{
84+
for (var row = 0; row < rows; row++)
85+
{
86+
var pixelRow = pixelAccessor.GetRowSpan(row);
87+
Debug.Assert(cols == pixelRow.Length, "Using pixelRow.Length allows the JIT to optimize away bounds checks");
88+
for (var col = 0; col < pixelRow.Length; col++)
89+
{
90+
ref var pixel = ref pixelRow[col];
91+
var alphaMask = erodeMask[col, row] / 255d;
92+
pixel.A = (byte) (alphaMask * pixel.A);
93+
//pixelRow[col] = pixel;
94+
}
95+
}
96+
});
97+
98+
// 测试代码
99+
bitmap.ProcessPixelRows(pixelAccessor
100+
=>
101+
{
102+
for (var row = 0; row < rows; row++)
103+
{
104+
var pixelRow = pixelAccessor.GetRowSpan(row);
105+
for (var col = 0; col < pixelRow.Length; col++)
106+
{
107+
var pixel = pixelRow[col];
108+
var a = pixel.A;
109+
_ = a;
110+
}
111+
}
112+
});
113+
}
114+
115+
/// <summary>
116+
/// 图像腐蚀
117+
/// </summary>
118+
/// <param name="source"></param>
119+
/// <param name="target"></param>
120+
/// <param name="row"></param>
121+
/// <param name="cols"></param>
122+
/// <param name="offsetX"></param>
123+
/// <param name="offsetY"></param>
124+
private static void BatchAlphaErode(byte[,] source, byte[,] target, int row, int cols, int offsetX, int offsetY)
125+
{
126+
var isNeedInitialize = true;
127+
var blackPointCols = new List<int>();
128+
129+
for (var col = offsetX; col < cols - offsetX; col++)
130+
{
131+
var minCol = col - offsetX;
132+
var maxCol = col + offsetX;
133+
var minRow = row - offsetY;
134+
var maxRow = row + offsetY;
135+
136+
if (isNeedInitialize)
137+
{
138+
for (var x = minCol; x <= maxCol; x++)
139+
{
140+
for (var y = minRow; y < maxRow; y++)
141+
{
142+
if (source[x, y] == 0)
143+
{
144+
blackPointCols.Add(x);
145+
break;
146+
}
147+
}
148+
}
149+
150+
isNeedInitialize = false;
151+
}
152+
else
153+
{
154+
blackPointCols.Remove(minCol - 1);
155+
for (var y = minRow; y < maxRow; y++)
156+
{
157+
if (source[maxCol, y] == 0)
158+
{
159+
blackPointCols.Add(maxCol);
160+
break;
161+
}
162+
}
163+
}
164+
165+
//腐蚀计算
166+
if (blackPointCols.Count == 0)
167+
{
168+
target[col, row] = byte.MaxValue;
169+
}
170+
}
171+
}
172+
173+
/// <summary>
174+
/// 使用归一化框过滤器模糊图像,是一种简单的模糊函数,是计算每个像素中对应核的平均值
175+
/// </summary>
176+
/// <param name="source"></param>
177+
/// <param name="target"></param>
178+
/// <param name="row"></param>
179+
/// <param name="rows"></param>
180+
/// <param name="cols"></param>
181+
/// <param name="offsetX"></param>
182+
/// <param name="offsetY"></param>
183+
private static void BatchAlphaBlur(byte[,] source, byte[,] target, int row, int rows, int cols, int offsetX,
184+
int offsetY)
185+
{
186+
var isNeedInitialize = true;
187+
var valueCache = new Dictionary<int, int>();
188+
189+
for (var col = 0; col < cols; col++)
190+
{
191+
var minCol = col - offsetX;
192+
var maxCol = col + offsetX;
193+
var minRow = row - offsetY;
194+
var maxRow = row + offsetY;
195+
196+
var count = (offsetX * 2 + 1) * (offsetY * 2 + 1);
197+
if (count == 0) count = 1;
198+
199+
if (isNeedInitialize)
200+
{
201+
for (var x = minCol; x <= maxCol; x++)
202+
{
203+
var value = 0;
204+
if (x > 0 && x < cols)
205+
{
206+
for (var y = minRow; y < maxRow; y++)
207+
{
208+
if (y > 0 && y < rows)
209+
{
210+
value += source[x, y];
211+
}
212+
}
213+
}
214+
215+
valueCache.Add(x, value);
216+
}
217+
218+
isNeedInitialize = false;
219+
}
220+
else
221+
{
222+
var value = 0;
223+
valueCache.Remove(minCol - 1);
224+
if (maxCol > 0 && maxCol < cols)
225+
{
226+
for (var y = minRow; y <= maxRow; y++)
227+
{
228+
if (y > 0 && y < rows)
229+
{
230+
value += source[maxCol, y];
231+
}
232+
}
233+
}
234+
235+
valueCache.Add(maxCol, value);
236+
}
237+
238+
//计算模糊
239+
var targetValue = valueCache.Values.Sum() / (double) count;
240+
target[col, row] = (byte) Math.Round(targetValue);
241+
}
242+
}
10243
}
501 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using SixLabors.ImageSharp;
2+
using SixLabors.ImageSharp.Formats;
3+
using SixLabors.ImageSharp.PixelFormats;
4+
5+
namespace DotNetCampus.MediaConverters.Tests;
6+
7+
internal static class TestFileProvider
8+
{
9+
public static Image<Rgba32> GetDefaultTestImage()
10+
{
11+
var imageFile = GetTestFile("file_example_PNG_500kB.png");
12+
using var fileStream = imageFile.OpenRead();
13+
return Image.Load<Rgba32>(new DecoderOptions(), fileStream);
14+
}
15+
16+
public static string GetTestFilePath(string fileName)
17+
{
18+
return System.IO.Path.Join(AppContext.BaseDirectory, "Assets", "TestFiles", fileName);
19+
}
20+
21+
public static FileInfo GetTestFile(string fileName)
22+
{
23+
return new FileInfo(GetTestFilePath(fileName));
24+
}
25+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Diagnostics;
2+
using SixLabors.ImageSharp;
3+
using SixLabors.ImageSharp.Formats.Png;
4+
using SixLabors.ImageSharp.PixelFormats;
5+
6+
namespace DotNetCampus.MediaConverters.Tests;
7+
8+
[TestClass]
9+
public static class TestHelper
10+
{
11+
[AssemblyInitialize]
12+
public static void AssemblyInit(TestContext context)
13+
{
14+
WorkingDirectory = Directory.CreateDirectory(Path.Join(context.TestRunDirectory, "Working"));
15+
}
16+
17+
private static DirectoryInfo WorkingDirectory { get; set; } = null!;
18+
19+
public static string SaveAsTestImageFile(this Image<Rgba32> image)
20+
{
21+
var file = Path.Join(WorkingDirectory.FullName, Path.GetRandomFileName() + ".png");
22+
using var fileStream = File.OpenWrite(file);
23+
image.SaveAsPng(fileStream, new PngEncoder()
24+
{
25+
ColorType = PngColorType.RgbWithAlpha
26+
});
27+
return file;
28+
}
29+
30+
public static void OpenFileInExplorer(string filePath)
31+
{
32+
if (File.Exists(filePath))
33+
{
34+
Process.Start(new ProcessStartInfo("explorer", $"\"{filePath}\"") { UseShellExecute = true });
35+
}
36+
else
37+
{
38+
throw new FileNotFoundException($"The file '{filePath}' does not exist.");
39+
}
40+
}
41+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using DotNetCampus.MediaConverters.Imaging.Effect;
2+
using SixLabors.ImageSharp;
3+
using SixLabors.ImageSharp.PixelFormats;
4+
5+
namespace DotNetCampus.MediaConverters.Tests.Imaging.Effect;
6+
7+
[TestClass()]
8+
public class SoftEdgeHelperTests
9+
{
10+
[TestMethod()]
11+
public void SetSoftEdgeMaskTest()
12+
{
13+
Image<Rgba32> bitmap = TestFileProvider.GetDefaultTestImage();
14+
var alphaRepresentation = bitmap.PixelType.AlphaRepresentation;
15+
16+
SoftEdgeHelper.SetSoftEdgeMask(bitmap, 50.0f);
17+
18+
var file = bitmap.SaveAsTestImageFile();
19+
20+
Assert.IsTrue(File.Exists(file));
21+
TestHelper.OpenFileInExplorer(file);
22+
}
23+
}

src/MediaConverters/MediaConverters.Tests/MediaConverters.Tests.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,10 @@
2121
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<None Update="Assets\TestFiles\**\*">
26+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27+
</None>
28+
</ItemGroup>
29+
2430
</Project>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=framework/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)