Compare commits

...

45 Commits

Author SHA1 Message Date
Samuele Lorefice
c65346aeec Added release build configs 2025-04-07 07:56:23 +02:00
Samuele Lorefice
a74ae1167e Updated gitignore 2025-04-07 07:51:39 +02:00
mm00
d7d2091d64 Merge remote-tracking branch 'origin/ILGpu' into ILGpu 2025-04-04 19:23:20 +02:00
mm00
ea28738280 added border to initial image used in final image calculation 2025-04-04 19:23:02 +02:00
Samuele Lorefice
2faab96a7d Merge remote-tracking branch 'origin/ILGpu' into ILGpu 2025-04-04 19:22:26 +02:00
Samuele Lorefice
8c5ca2ce21 Small kernel fixes 2025-04-04 19:22:05 +02:00
mm00
d0fbd31c1d fixed final image masking 2025-04-04 19:20:43 +02:00
mm00
5cf8612699 Fixed directional blur 2025-04-03 20:49:44 +02:00
mm00
dfa2cf3f31 added luma threshould 2025-04-03 19:28:26 +02:00
mm00
64b7eb9dcc added SDF normalization 2025-04-03 19:28:20 +02:00
Samuele Lorefice
d652c63586 Added editor config 2025-04-03 19:14:58 +02:00
Samuele Lorefice
9614283dc8 Cleanup and added formatting rules. Reformatted 2025-04-03 19:02:50 +02:00
mm00
b072eea732 Merge remote-tracking branch 'origin/ILGpu' into ILGpu 2025-04-03 18:50:57 +02:00
Samuele Lorefice
925e4c9989 Fixed gradient, more cleanup 2025-04-03 18:48:50 +02:00
mm00
69d3b517f3 Merge remote-tracking branch 'origin/ILGpu' into ILGpu 2025-04-03 18:48:29 +02:00
mm00
2d1368a908 Added directional blur 2025-04-03 18:48:21 +02:00
Samuele Lorefice
3b319da80b Cleaned up main 2025-04-03 18:38:56 +02:00
mm00
db54b80b5a Merge remote-tracking branch 'origin/ILGpu' into ILGpu 2025-04-03 18:09:29 +02:00
mm00
281e0f4aee Fixed sdf calculation via ILGPU 2025-04-03 18:09:17 +02:00
Samuele Lorefice
366a5c1cab Added Gradient Kernel 2025-04-03 18:02:43 +02:00
Samuele Lorefice
86c0e97672 Added gradient kernel 2025-04-03 17:55:11 +02:00
Samuele Lorefice
6fd430a670 misc fixes 2025-04-03 17:06:50 +02:00
Samuele Lorefice
664c5e02fe Added SDF kernel 2025-04-02 20:26:28 +02:00
Samuele Lorefice
1d64749e76 Started converting the methods to parallel processing enabled kernels 2025-04-02 19:54:38 +02:00
Samuele Lorefice
0ef4ad1c4a Added ILGpu 2025-04-02 19:54:01 +02:00
mm00
1bdead0750 Added first image to start SDF and fixed blending 2025-04-01 19:49:39 +02:00
mm00
8173327c79 Multiplied channels by alpha when less than Vector4 2025-04-01 19:49:21 +02:00
mm00
7c484a9af3 completed refactoring, added 32x32 test images 2025-04-01 18:48:58 +02:00
mm00
f954a77cc5 Merge remote-tracking branch 'origin/master' 2025-04-01 17:49:00 +02:00
mm00
f7561c77b4 Added new test image 2025-04-01 17:48:49 +02:00
Samuele Lorefice
537bcc0305 Added multiple image formats support 2025-04-01 17:48:03 +02:00
Samuele Lorefice
a77e9b7989 Giga Refactor 2025-03-29 00:51:52 +01:00
Samuele Lorefice
78efd4fcc2 Removed imageMagick 2025-03-29 00:42:21 +01:00
Samuele Lorefice
e158cfc95b Expanded LoadImage to cover all channel counts 2025-03-28 22:10:10 +01:00
Samuele Lorefice
ccaae9befd Added image util class 2025-03-28 21:57:59 +01:00
Samuele Lorefice
f3f01a2f85 Refactor 2025-03-28 20:51:38 +01:00
Samuele Lorefice
571bd81c0d Merge remote-tracking branch 'origin/master' 2025-03-28 19:35:52 +01:00
Samuele Lorefice
ca3e0cdd39 Shortened Console update line method 2025-03-28 19:35:45 +01:00
mm00
f3fca33606 fixed gradient calculation 2025-03-28 19:30:43 +01:00
mm00
4c45884c96 Merge remote-tracking branch 'origin/master' 2025-03-28 18:51:46 +01:00
mm00
92529b562e Fixed sdf usage in gradient and added test cases 2025-03-28 18:51:40 +01:00
Samuele Lorefice
84a49bb2a8 Merge remote-tracking branch 'origin/master' 2025-03-28 18:49:23 +01:00
Samuele Lorefice
01a92b2099 Improved logging 2025-03-28 18:46:38 +01:00
mm00
1c136880aa Using SIMD acceleration, added core usage limit 2025-03-27 19:20:54 +01:00
mm00
0dc04043b1 added image files 2025-03-27 19:19:32 +01:00
28 changed files with 4890 additions and 281 deletions

4012
.editorconfig Normal file

File diff suppressed because it is too large Load Diff

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
/_ReSharper.Caches/
publish/

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Release Build Linux-x64" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" delete_existing_files="true" include_native_libs_for_self_extract="true" platform="Any CPU" produce_single_file="true" runtime="linux-x64" target_folder="$PROJECT_DIR$/SDFMapCreator/publish/linux-x64" target_framework="net8.0" uuid_high="-7972981449231152312" uuid_low="-5206031561210231144" />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Release Build Win-x64" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" delete_existing_files="true" include_native_libs_for_self_extract="true" platform="Any CPU" produce_single_file="true" runtime="win-x64" target_folder="$PROJECT_DIR$/SDFMapCreator/publish/win-x64" target_framework="net8.0" uuid_high="-7972981449231152312" uuid_low="-5206031561210231144" />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,19 @@
<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">
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_LOCK/@EntryValue">NotRequired</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_REDUNDANT/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/MODIFIERS_ORDER/@EntryValue">public private protected internal file new static abstract virtual override readonly sealed extern unsafe volatile async required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_EXPR_METHOD_ON_SINGLE_LINE/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_INVOCABLE/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FOR_STMT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_ARROW_WITH_EXPRESSIONS/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">151</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,14 @@
<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">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAccelerator_002EAllocations_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa3e5a0b5353a4a59d3be7bb386db3c46069739eacca1fc6b7323dca1ee7fd_003FAccelerator_002EAllocations_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACPUMultiprocessor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fmm00_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5d1e647f49ea4d7aa141f19476dc7451ae33d6321d7fb675b45f9b836878ca1a_003FCPUMultiprocessor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntryPointDescription_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F32b5380d8ca1aa7219c1622702a3e927b2bb32c9a53b43e12bb5a4af9a2862d_003FEntryPointDescription_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_007BTPixel_007D_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F98f0ece83ba33754ab932bd7b5c712d12f3a59029f9f14067f553a3a318c8f_003FImage_007BTPixel_007D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fmm00_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8056cd3f452fefb9834f05cdb275b762dd41f27b7766cd71174e78592dc495b_003FMonitor_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APixelTypeInfo_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc3cfdca1fb93eb6df5e51a81da5df646adfab8b862fd1a07ee5d247b49c5179_003FPixelTypeInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002EUnix_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fmm00_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F9cf5f68d759deefc91b9c48c5ac3dd27708bb7dc38d0c485661fff5ce15b82_003FSafeFileHandle_002EUnix_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002EWindows_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F261ea83c988816e3d8fe76b15b7ac6c10af64b8f9e739854f83c137c8ba9_003FSafeFileHandle_002EWindows_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2c8e7ca976f350cba9836d5565dac56b11e0b56656fa786460eb1395857a6fa_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVector3_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fmm00_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6edafe13d8727aa238b865f5dc91dbc984b5abfbc60bece3744f6311c2c_003FVector3_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;
&lt;Assembly Path="/mnt/nvme2/Railgun/SDFMapCreator/SDFMapCreator/bin/Debug/net8.0/Magick.NET-Q16-HDRI-OpenMP-x64.dll" /&gt;
&lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary>

185
SDFMapCreator/ImageUtil.cs Normal file
View File

@@ -0,0 +1,185 @@
using System.Numerics;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace SDFMapCreator;
public static class ImageUtil {
public static T[,] LoadImage<T>(string path) where T : struct, IEquatable<T> {
var image = SixLabors.ImageSharp.Image.Load(path);
return image switch {
Image<Rgba64> img => img.ProcessPixelsRgba64<T>(),
Image<Rgb24> img => img.ProcessPixelsRgb24<T>(),
Image<Rgba32> img => img.ProcessPixelsRgba32<T>(),
Image<Rgb48> img => img.ProcessPixelsRgb48<T>(),
_ => throw new NotSupportedException($"Image format not supported")
};
}
static T[,] ProcessPixelsRgba64<T>(this Image<Rgba64> image) where T : struct, IEquatable<T> {
var max = 65535f;
var width = image.Width;
var height = image.Height;
var result = new T[image.Width, image.Height];
image.ProcessPixelRows(accessor => {
//we use Y as the row index and X as the column index
for (var y = 0; y < height; y++) {
var span = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++) {
switch (result) {
case float[,] f:
f[x, y] = span[x].R / max * (span[x].A / max);
break;
case Vector2[,] f:
f[x, y] = new Vector2(span[x].R / max, span[x].G / max) * new Vector2(span[x].A / max);
break;
case Vector3[,] f:
f[x, y] = new Vector3(span[x].R / max, span[x].G / max, span[x].B / max) * new Vector3(span[x].A / max);
break;
case Vector4[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max);
break;
}
}
}
});
return result;
}
static T[,] ProcessPixelsRgb24<T>(this Image<Rgb24> image) where T : struct, IEquatable<T> {
var max = 255f;
var width = image.Width;
var height = image.Height;
var result = new T[image.Width, image.Height];
image.ProcessPixelRows(accessor => {
//we use Y as the row index and X as the column index
for (var y = 0; y < height; y++) {
var span = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++) {
switch (result) {
case float[,] f:
f[x, y] = span[x].R / max;
break;
case Vector2[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max);
break;
case Vector3[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max);
break;
case Vector4[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f);
break;
}
}
}
});
return result;
}
static T[,] ProcessPixelsRgba32<T>(this Image<Rgba32> image) where T : struct, IEquatable<T> {
var max = 255f;
var width = image.Width;
var height = image.Height;
var result = new T[image.Width, image.Height];
image.ProcessPixelRows(accessor => {
//we use Y as the row index and X as the column index
for (var y = 0; y < height; y++) {
var span = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++) {
switch (result) {
case float[,] f:
f[x, y] = span[x].R / max * (span[x].A / max);
break;
case Vector2[,] f:
f[x, y] = new Vector2(span[x].R / max, span[x].G / max) * new Vector2(span[x].A / max);
break;
case Vector3[,] f:
f[x, y] = new Vector3(span[x].R / max, span[x].G / max, span[x].B / max) * new Vector3(span[x].A / max);
break;
case Vector4[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max);
break;
}
}
}
});
return result;
}
static T[,] ProcessPixelsRgb48<T>(this Image<Rgb48> image) where T : struct, IEquatable<T> {
var max = 65535f;
var width = image.Width;
var height = image.Height;
var result = new T[image.Width, image.Height];
image.ProcessPixelRows(accessor => {
//we use Y as the row index and X as the column index
for (var y = 0; y < height; y++) {
var span = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++) {
switch (result) {
case float[,] f:
f[x, y] = span[x].R / max;
break;
case Vector2[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max);
break;
case Vector3[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max);
break;
case Vector4[,] f:
f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f);
break;
}
}
}
});
return result;
}
public static void SaveImage<T>(this T[,] array, string path) where T : struct, IEquatable<T> {
var width = array.GetLength(0);
var height = array.GetLength(1);
uint channels = array switch {
float[,] => 1,
Vector2[,] => 2,
Vector3[,] => 3,
Vector4[,] => 4,
_ => throw new NotSupportedException($"Type {typeof(T)} is not supported.")
};
Console.Write($"Writing image {path}...");
using Image<Rgb48> image = new(width, height);
image.ProcessPixelRows(accessor => {
for (var y = 0; y < height; y++) {
var span = accessor.GetRowSpan(y);
for (var x = 0; x < width; x++) {
switch (array) {
case float[,] f:
span[x] = new((ushort)(f[x, y] * 65535), (ushort)(f[x, y] * 65535), (ushort)(f[x, y] * 65535));
break;
case Vector2[,] f:
span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), 0);
break;
case Vector3[,] f:
span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), (ushort)(f[x, y].Z * 65535));
break;
case Vector4[,] f:
span[x] = new((ushort)(f[x, y].X * 65535), (ushort)(f[x, y].Y * 65535), (ushort)(f[x, y].Z * 65535));
break;
}
}
}
});
Console.WriteLine($"Done!");
image.Save(path);
}
static void ImageData(SixLabors.ImageSharp.Image image1, string path) {
Console.WriteLine(
$"""
Image file: {path}
Resolution: {image1.Width}x{image1.Height}
Total Pixels: {image1.Width * image1.Height} |{"NaN"} channels, {image1.PixelType.BitsPerPixel / 4} bits per channel
""");
}
}

View File

@@ -1,326 +1,258 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageMagick;
public struct float2(float x, float y) {
public float x = x, y = y;
}
namespace SDFMapCreator;
public struct float3(float r, float g, float b) {
public float r = r, g = g, b = b;
public float3(float value) : this(value, value, value) {}
public float3(float r, float g) : this(r, g, 0f) {}
}
public struct float4(float r, float g, float b, float a) {
public float r = r, g = g, b = b, a = a;
public float4(float value) : this(value, value, value, value) {}
public float4(float r, float g, float b) : this(r, g, b, 1f) {}
}
public record ImageData(MagickImage Image, float3[,] Pixels, List<float2> Edges);
public record MaskData(float3[,] Mask, ImageData A, List<float2> Edges);
public record SDFData(float3[,] SDF);
public static class ArrayExt {
public static float3[,] To2DFloat3(this float[] array, uint width, uint height) {
float3[,] result = new float3[width, height];
for(int i = 0; i < width*height; i++) {
uint x = (uint)(i % width);
uint y = (uint)(i / width);
result[y, x] = new (array[i*3], array[i*3+1], array[i*3+2]);
}
return result;
}
public static float[] ToFloatArray(this float3[,] array) {
float[] result = new float[array.GetLength(0) * array.GetLength(1) * 3];
for(int x = 0; x < array.GetLength(0); x++) {
for(int y = 0; y < array.GetLength(1); y++) {
result[x*array.GetLength(1)*3 + y*3] = array[x, y].r;
result[x*array.GetLength(1)*3 + y*3+1] = array[x, y].g;
result[x*array.GetLength(1)*3 + y*3+2] = array[x, y].b;
}
}
return result;
}
}
public class Program {
private const float MAX = 65535f;
private const float MIN = 0f;
private const bool outputMasks = false;
private const bool outputSDFs = false;
private const bool outputGradients = false;
static List<ImageData> Images = new();
static List<MaskData> Masks = new();
public static class Program {
const float MAX = 1f;
const float MIN = 0f;
static readonly int MAX_THREADS = Environment.ProcessorCount - 2;
static bool debug = true;
static readonly ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = MAX_THREADS };
static List<Image> Images = new();
static List<MaskData> ImageMasks = new();
static List<TransitionMaskData> TransitionMasks = new();
static List<SDFData> SDFs = new();
static List<float3[,]> Gradients = new();
static void LoadImage(string imgPath) {
var image = new MagickImage(imgPath);
float3[,] pixels;
if(image.Channels.Count() ==4)
//skip ever 4th value if we have an alpha channel
pixels = image.GetPixels().ToArray()!
.Where((_, i) => (i + 1) % 4 != 0).ToArray()
.To2DFloat3(image.Width, image.Height);
else
pixels = image.GetPixels().ToArray()!
.To2DFloat3(image.Width, image.Height);
Images.Add(new (image, pixels, new()));
Console.WriteLine($"Loaded image: {imgPath}");
ImageData(image, image.GetPixels());
}
static List<Vector3[,]> Gradients = new();
static readonly SdfKernels kernels = new();
static uint width;
static uint height;
static void ConsoleUpdateLine(string s) => Console.Write("\r" + s);
public static void Main(string[] args) {
Console.WriteLine("Reading images...");
//foreach image in arguments load the image
if (debug) {
if (!Directory.Exists("Debug")) Directory.CreateDirectory("Debug");
Console.WriteLine("Debug mode enabled.");
}
LoadImage("01.png");
LoadImage("02.png");
LoadImage("03.png");
LoadImage("04.png");
LoadImage("05.png");
LoadImage("06.png");
LoadImage("07.png");
LoadImage("08.png");
//LoadImage("1.png");
//LoadImage("2.png");
var imagesPath = "images";
for (var i = 0; i < 8; i++) {
var pixels = ImageUtil.LoadImage<Vector3>($"./{imagesPath}{Path.DirectorySeparatorChar}{i + 1:00}.png");
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
}
/*
var pixels = ImageUtil.LoadImage<Vector3>($"./sphereempty.png");
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
pixels = ImageUtil.LoadImage<Vector3>($"./spherehalf.png");
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
pixels = ImageUtil.LoadImage<Vector3>($"./spherecut.png");
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
pixels = ImageUtil.LoadImage<Vector3>($"./spherefull.png");
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
*/
//check if all the images in Images are the same resolution
if (Images.Select(img => (img.Image.Width, img.Image.Height)).Distinct().Count() > 1) {
if (Images.Select(img => (img.Width, img.Height)).Distinct().Count() > 1) {
Console.WriteLine("Error: Not all images have the same resolution.");
Environment.Exit(1);
}
Console.WriteLine("Creating masks...");
Masks.Add(new (SelfMask(Images[0].Pixels, Images[0].Image.Width, Images[0].Image.Height), Images[0], new()));
//for each image pair, create a mask
for (int i = 1; i < Images.Count; i++) {
var mask = GetABMask(Images[i-1].Pixels, Images[i].Pixels, Images[i].Image.Width, Images[i].Image.Height);
Masks.Add(new(mask, Images[i], new()));
}
Console.WriteLine("Edge detecting masks...");
//EdgeDetect all masks
foreach (var t in Masks) { EdgeDetect(t); }
if(outputMasks) {
Console.WriteLine("Writing masks...");
for (int i = 0; i < Masks.Count; i++) {
var mask = new MagickImage(MagickColors.Black, (uint)Masks[i].Mask.GetLength(0), (uint)Masks[i].Mask.GetLength(1));
mask.GetPixels().SetPixels(Masks[i].Mask.ToFloatArray());
mask.Write($"mask{i}.png", MagickFormat.Png24);
}
}
Console.WriteLine("Creating SDFs...");
for (var i = 0; i < Masks.Count; i++) {
var mask = Masks[i];
SDFs.Add(SDF(mask));
if(!outputSDFs) continue;
var sdf = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
sdf.GetPixels().SetPixels(SDFs[i].SDF.ToFloatArray());
sdf.Write($"sdf{i}.png", MagickFormat.Png48);
}
Console.WriteLine("Creating gradients...");
for (var i = 0; i < Masks.Count - 1; i++) {
var gradientData = Gradient(Masks[i], SDFs[i], SDFs[i + 1]);
Gradients.Add(gradientData);
if(!outputGradients) continue;
var gradient = new MagickImage(MagickColors.Black, (uint)Masks[i].Mask.GetLength(0), (uint)Masks[i].Mask.GetLength(1));
gradient.GetPixels().SetPixels(gradientData.ToFloatArray());
gradient.Write($"gradient{i}.png", MagickFormat.Png24);
}
Console.WriteLine("Preparing transitions...");
//for each gradient read the corresponding mask
//for each pixel in the mask, lerp the pixel value with the gradient value
//write the result to a new image
int width = Masks[0].Mask.GetLength(0);
int height = Masks[0].Mask.GetLength(1);
for (var i = 0; i < Gradients.Count; i++) {
var mask = Masks[i + 1];
var gradient = Gradients[i];
var transition = new float3[width, height];
for (var x = 0; x < mask.Mask.GetLength(0); x++) {
for (var y = 0; y < mask.Mask.GetLength(1); y++) {
if (mask.Mask[x, y].r == 0) continue;
transition[x, y] = new(Lerp(Images[i].Pixels[x,y].r, MAX, gradient[x,y].r));
width = (uint)Images[0].Width;
height = (uint)Images[0].Height;
// sum all images together
if(debug) Images[0].Pixels.SaveImage("Debug/Sum0.png");
for (int i = 1; i < Images.Count; i++) {
for (int x = 0; x < Images[i].Width; x++) {
for (int y = 0; y < Images[i].Height; y++) {
Images[i].Pixels[x, y].X = MathF.Min(Images[i - 1].Pixels[x, y].X + Images[i].Pixels[x, y].X, MAX);
Images[i].Pixels[x, y].Y = MathF.Min(Images[i - 1].Pixels[x, y].Y + Images[i].Pixels[x, y].X, MAX);
Images[i].Pixels[x, y].Z = MathF.Min(Images[i - 1].Pixels[x, y].Z + Images[i].Pixels[x, y].X, MAX);
}
}
var transitionImage = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
transitionImage.GetPixels().SetPixels(transition.ToFloatArray());
transitionImage.Write($"transition{i}.png", MagickFormat.Png24);
if(debug)Images[i].Pixels.SaveImage($"Debug/Sum{i}.png");
}
Console.WriteLine("Creating masks...");
for (var i = 0; i < Images.Count; i++) { //for each image pair, create a mask
var selfMask = SelfMask(Images[i].Pixels);
ImageMasks.Add(new(selfMask, Images[i], new()));
if (debug) selfMask.SaveImage($"Debug/selfMask{i}.png");
if (i >= Images.Count - 1) continue;
ConsoleUpdateLine($"Creating mask {i}...");
var mask = GetABMask(Images[i].Pixels, Images[i + 1].Pixels);
TransitionMasks.Add(new(mask, Images[i], Images[i + 1]));
}
//EdgeDetect all masks
Console.WriteLine("\nEdge detecting masks...");
foreach (var mask in ImageMasks) {
ConsoleUpdateLine($"Edge detecting mask {ImageMasks.IndexOf(mask)}...");
EdgeDetect(mask);
}
if (debug)
for (var i = 0; i < TransitionMasks.Count; i++)
ImageMasks[i].Mask.SaveImage($"Debug/mask{i}.png");
Console.WriteLine("Creating SDFs...");
for (var i = 0; i < ImageMasks.Count; i++) {
var mask = ImageMasks[i];
SDFs.Add(SDF(mask));
if (debug) SDFs[i].SDF.SaveImage($"Debug/sdf{i}.png");
}
Console.WriteLine("Creating gradients...");
for (var i = 0; i < TransitionMasks.Count; i++) {
ConsoleUpdateLine($"Generating gradient {i}...");
var gradientData = Gradient(TransitionMasks[i], SDFs[i], SDFs[i + 1]);
Gradients.Add(gradientData);
if (debug) gradientData.SaveImage($"Debug/gradient{i}.png");
}
// generate final image
var finalImage = new Vector3[width, height];
var currStep = 0f;
var stepIncrement = MAX / Gradients.Count;
for (var x = 0; x < width; x++)
for (var y = 0; y < height; y++)
finalImage[x, y] = new(ImageMasks[0].Mask[x, y].X != 0 || ImageMasks[0].Mask[x, y].Y > 0 ? MAX : MIN);
for (var i = 0; i < Gradients.Count; i++) {
var mask = ImageMasks[i + 1];
var gradient = Gradients[i];
Console.WriteLine($"Applying gradient {i}..., {currStep} -> {currStep + stepIncrement}");
for (var x = 0; x < mask.Mask.GetLength(0); x++) {
for (var y = 0; y < mask.Mask.GetLength(1); y++) {
if (mask.Mask[x,y].X == 0) continue;
var pixel = new Vector3(Remap(gradient[x, y].X, MIN, MAX, 1.0f - currStep,
1.0f - (currStep + stepIncrement)));
if (pixel.X > finalImage[x, y].X) finalImage[x, y] = pixel;
}
}
currStep += stepIncrement;
}
finalImage.SaveImage("Debug/Final.png");
// apply directional blur
var iterations = 1;
var radius = 100f;
var step = .5f;
var sigma = 1f;
var totalMask = SelfMask(Images[^1].Pixels);
totalMask.SaveImage("Debug/TotalMask.png");
for (var i = 0; i < iterations; i++) {
Console.WriteLine($"Applying directional blur {i + 1}/{iterations}...");
finalImage = DirectionalBlur(finalImage, totalMask, radius, step, sigma);
}
finalImage.SaveImage("finalBlur.png");
Console.WriteLine("Done!");
}
private static void EdgeDetect(MaskData maskData) {
uint width = maskData.A.Image.Width;
uint height = maskData.A.Image.Height;
int iterCount = 0;
static void EdgeDetect(MaskData maskData) {
var sw = new Stopwatch();
sw.Start();
Console.WriteLine("Running edge detection...");
Parallel.For(0, width * height, (i) => {
int x = (int)(i % width);
int y = (int)(i / width);
if (!EdgeKernel(maskData.Mask, x, y, width, height)) return;
var color = maskData.Mask[x, y];
color.g = MAX;
maskData.Mask[x, y] = color;
lock(maskData.Edges) maskData.Edges.Add(new(x, y));
iterCount++;
if (iterCount % (width * height / 100) == 0) {
Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
}
kernels.EdgeDetect(maskData.Mask, out var temp);
maskData.Mask = temp;
Parallel.For(0, width * height, parallelOptions, (i) => {
if (maskData.Mask[i % width, i / width].Y == 0) return;
// ReSharper disable once PossibleLossOfFraction
lock (maskData.Edges) maskData.Edges.Add(new(i % width, i / width));
});
sw.Stop();
Console.WriteLine($"Edge pixels: {maskData.Edges.Count} | {maskData.Edges.Count/sw.ElapsedMilliseconds} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s");
Console.WriteLine(
$"\nEdge pixels: {maskData.Edges.Count} | {width * height / (sw.ElapsedMilliseconds + 1)} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s");
}
static float3[,] Gradient(MaskData mask, SDFData sdfA, SDFData sdfB) {
uint width = mask.A.Image.Width;
uint height = mask.A.Image.Height;
int iterCount = 0;
static Vector3[,] Gradient(TransitionMaskData mask, SDFData sdfA, SDFData sdfB) {
var sw = new Stopwatch();
sw.Start();
float3[,] temp = new float3[width, height];
Console.WriteLine("Running edge detection...");
Parallel.For(0, width * height, (i) => {
/*Vector3[,] temp = new Vector3[width, height];
var min = MAX;
var max = MIN;
Console.WriteLine("Running gradient generation...");
Parallel.For(0, width * height, parallelOptions, (i) =>
{
int x = (int)(i % width);
int y = (int)(i / width);
var a = (sdfA.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3;
var b = (sdfB.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3;
if (mask.Mask[x, y].X == 0 || mask.Mask[x, y].Y > 0) return;
var a = sdfA.SDF[x, y].X;
var b = sdfB.SDF[x, y].X;
var gradient = a / (a + b);
temp[x, y] = new(Remap(gradient, 0, 1, MIN, MAX));
});
Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
var min = temp.Cast<float3>().Min(x => x.r);
var max = temp.Cast<float3>().Max(x => x.r);
Console.WriteLine($"Min: {min} | Max: {max}");
if (gradient < min) min = gradient;
if (gradient > max) max = gradient;
});*/
kernels.Gradient(mask.Mask, sdfA.SDF, sdfB.SDF, out var temp);
Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / (float)sw.Elapsed.TotalSeconds:N0} pixels/s)");
return temp;
}
static SDFData SDF(MaskData mask) {
var width = (uint)mask.Mask.GetLength(0);
var height = (uint)mask.Mask.GetLength(1);
var temp = new float3[width, height];
float AbsMax = MIN;
int iterCount = 0;
var sw = new Stopwatch();
sw.Start();
Parallel.For(0, width * height, (i) => {
//convert 1D index to 2D index
var x = (int)(i % width);
var y = (int)(i / width);
float2 p = new(x, y);
float minDist = MAX; //initialize the minimum distance to the maximum possible value
//loop through all the pixels in the mask
foreach (var edge in mask.Edges) {
float dist = EuclideanDistance(p, edge);
if (dist < minDist) minDist = dist;
}
temp[x, y] = new(float.Abs(minDist));
if (minDist > AbsMax) AbsMax = minDist;
iterCount++;
if (iterCount % (width * height / 100) == 0) {
Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
}
});
kernels.Sdf(mask.Edges.ToArray(), (int)width, (int)height, out var temp);
Console.WriteLine($"\nSDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine($"SDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
sw.Restart();
Parallel.For(0, width * height, (i) => {
var absMax = 0f;
foreach (var pixel in temp) {
if (pixel.X > absMax) absMax = pixel.X;
}
Parallel.For(0, width * height, parallelOptions, (i) => {
//convert 1D index to 2D index
var x = (int)(i % width);
var y = (int)(i / width);
temp[x, y] = new(Remap(temp[x, y].r, 0, AbsMax, MIN, MAX));
temp[x, y] = new(Remap(temp[x, y].X, 0, absMax, MIN, MAX));
});
Console.WriteLine($"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine("AbsMax: " + AbsMax);
sw.Stop();
Console.WriteLine(
$"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / sw.Elapsed.TotalSeconds:N0} pixels/s)");
return new(temp);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float EuclideanDistance(float2 a, float2 b) =>
MathF.Sqrt(MathF.Pow(a.x - b.x, 2) + MathF.Pow(a.y - b.y, 2));
private static void ImageData(MagickImage image1, IPixelCollection<float> pixels1) {
static Vector3[,] DirectionalBlur(Vector3[,] image, Vector3[,] mask, float radius = 3f, float step = .5f, float sigma = 1f) {
var sw = new Stopwatch();
sw.Start();
kernels.DirectionalBlur(image, mask, out var temp, radius, step, sigma);
Console.WriteLine(
$"""
Image file: {image1.Format.ToString()}
Resolution: {image1.Width}x{image1.Height}
Total Pixels: {pixels1.Count()} |{pixels1.Channels} channels, {image1.Depth} bits per channel
""");
}
static float3[,] GetABMask(float3[,] A, float3[,] B, uint resX, uint resY) {
var temp = new float3[resX, resY];
Parallel.For(0, resX*resY, (i) => {
uint x = (uint)(i % resX);
uint y = (uint)(i / resX);
var pixelA = A[x, y];
var pixelB = B[x, y];
float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3;
float lumaB = (pixelB.r+pixelB.g+pixelB.b)/3;
float resultPixel = lumaB > lumaA ? MAX : MIN;
temp[x, y] = new(resultPixel, 0, 0);
});
return temp;
}
static float3[,] SelfMask(float3[,] A, uint resX, uint resY) {
var temp = new float3[resX, resY];
Parallel.For(0, resX*resY, (i) => {
uint x = (uint)(i % resX);
uint y = (uint)(i / resX);
var pixelA = A[x, y];
float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3;
float resultPixel = lumaA > 0 ? MAX : MIN;
temp[x, y] = new(resultPixel, 0, 0);
});
$"Directional Blur Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / sw.Elapsed.TotalSeconds:N0} pixels/s)");
return temp;
}
static bool EdgeKernel(float3[,] mask, int x, int y, uint width, uint height) {
//if we are already empty, return false
if (mask[x, y].r == 0) return false;
//if we are on the edge of the image, return false
if (x == 0 || y == 0 || x == width - 1 || y == height - 1) return false;
//check the 3x3 kernel
for (int xi = x - 1; xi <= x + 1; xi++) {
for (int yi = y - 1; yi <= y + 1; yi++) {
if (xi < 0 || xi >= width || yi < 0 || yi >= height)
continue; //skip out of bounds pixels
if (mask[xi, yi].r == 0)
return true; //if we find a black pixel, return true
}
}
//if we didn't find any black pixels, return false
return false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static float EuclideanDistance(Vector2 a, Vector2 b) =>
MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2));
static Vector3[,] GetABMask(Vector3[,] A, Vector3[,] B) {
kernels.ABMask(A, B, out var temp);
return temp;
}
static Vector3[,] SelfMask(Vector3[,] A) {
kernels.SelfMask(A, out var temp);
return temp;
}
static T Lerp<T>(T a, T b, float t)
where T : INumber<T>, IMultiplyOperators<T, float, T>, IAdditionOperators<T, T, T>
=> a * (1 - t) + b * t;
where T : INumber<T>, IMultiplyOperators<T, float, T>, IAdditionOperators<T, T, T>
=> a * (1 - t) + b * t;
static T Remap<T>(T value, T min, T max, T newMin, T newMax)
where T : INumber<T>, ISubtractionOperators<T, T, T>, IMultiplyOperators<T, T, T>, IAdditionOperators<T, T, T>
=> (value - min) / (max - min) * (newMax - newMin) + newMin;
=> (value - min) / (max - min) * (newMax - newMin) + newMin;
}

14
SDFMapCreator/Records.cs Normal file
View File

@@ -0,0 +1,14 @@
using System.Numerics;
namespace SDFMapCreator;
public record Image(Vector3[,] Pixels, int Width, int Height);
public record MaskData(Vector3[,] Mask, Image Image, List<Vector2> Edges) {
public Vector3[,] Mask { get; set; } = Mask;
public List<Vector2> Edges { get; set; } = Edges;
}
public record SDFData(Vector3[,] SDF);
public record TransitionMaskData(Vector3[,] Mask, Image ImageA, Image ImageB);

View File

@@ -8,19 +8,67 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-HDRI-OpenMP-x64" Version="14.5.0" />
<PackageReference Include="ILGPU" Version="1.5.2"/>
<PackageReference Include="ILGPU.Algorithms" Version="1.5.2"/>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7"/>
<PackageReference Include="System.Numerics.Vectors" Version="4.6.1"/>
</ItemGroup>
<ItemGroup>
<None Update="1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestPattern.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestPattern.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="images\01.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\02.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\03.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\04.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\05.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\06.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\07.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="images\08.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherecut.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherefull.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherehalf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherehalf32.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherefull32.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="spherecut32.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="sphereempty.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,167 @@
using System.Numerics;
using ILGPU;
using ILGPU.Runtime;
namespace SDFMapCreator;
public partial class SdfKernels {
private const float LUMA_THRESHOLD = 0.0f;
static void SelfMaskKernel(Index2D index, ArrayView2D<Vector3, Stride2D.DenseX> input, ArrayView2D<Vector3, Stride2D.DenseX> mask) {
var x = index.X;
var y = index.Y;
var value = input[x, y];
var lumaA = value.X;
var r = lumaA > LUMA_THRESHOLD ? 1f : 0f;
mask[x, y] = new(r, 0f, 0f);
}
static void ABMaskKernel(Index2D index,
ArrayView2D<Vector3, Stride2D.DenseX> A,
ArrayView2D<Vector3, Stride2D.DenseX> B,
ArrayView2D<Vector3, Stride2D.DenseX> mask
) {
var x = index.X;
var y = index.Y;
var valueA = A[x, y];
var valueB = B[x, y];
var lumaA = valueA.X;
var lumaB = valueB.X;
var r = lumaB - lumaA > LUMA_THRESHOLD ? 1f : 0f;
mask[x, y] = new(r, 0f, 0f);
}
static void EdgeKernel(Index2D index,
ArrayView2D<Vector3, Stride2D.DenseX> mask,
uint width, uint height
) { // early exit if not on mask
if (mask[index].X == 0f) return;
var x = index.X;
var y = index.Y;
//if we are on the edge of the image, return false
if (x == 0 || y == 0 || x == width - 1 || y == height - 1) {
mask[index].Y = 1f; //set the edge flag
return;
}
//check the 3x3 kernel
for (var xi = x - 1; xi <= x + 1; xi++) {
for (var yi = y - 1; yi <= y + 1; yi++) {
if (xi < 0 || xi >= width || yi < 0 || yi >= height)
continue; //skip out of bounds pixels
if (mask[xi, yi].X == 0f)
mask[index].Y = 1f; //if we find a black pixel, return true
}
}
}
static void SdfKernel(Index2D index,
ArrayView2D<Vector3, Stride2D.DenseX> sdf,
ArrayView1D<Vector2, Stride1D.Dense> edges,
int width, int height
) {
Vector2 pos = new((float)index.X / width, (float)index.Y / height);
var minDist = 2f;
var count = edges.IntExtent.Size;
for (var i = 0; i < count; i++) {
Vector2 edgeNrm = new(edges[i].X / width, edges[i].Y / height);
var dist = Vector2.Distance(pos, edgeNrm);
if (dist < minDist) minDist = dist;
}
if (minDist > 1f) minDist = 1f;
sdf[index] = new(minDist);
}
static void GradientKernel(Index2D index,
ArrayView2D<Vector3, Stride2D.DenseX> mask,
ArrayView2D<Vector3, Stride2D.DenseX> sdfa,
ArrayView2D<Vector3, Stride2D.DenseX> sdfb,
ArrayView2D<Vector3, Stride2D.DenseX> gradient
) { //early exit if not on mask
if (mask[index].X == 0f) return;
var a = sdfa[index].X;
var b = sdfb[index].X;
gradient[index] = new(a / (a + b));
}
static Vector3 SampleBilinear(ArrayView2D<Vector3, Stride2D.DenseX> image, float x, float y) {
int width = image.IntExtent.X;
int height = image.IntExtent.Y;
var x0 = (int)x;
var y0 = (int)y;
var x1 = x0 + 1;
var y1 = y0 + 1;
if (x0 < 0 || x1 >= width || y0 < 0 || y1 >= height) return Vector3.Zero;
var a = new Vector2(x - x0, y - y0);
var b = new Vector2(1f - a.X, 1f - a.Y);
return Vector3.Lerp(
Vector3.Lerp(image[x0, y0], image[x1, y0], a.X),
Vector3.Lerp(image[x0, y1], image[x1, y1], a.X),
a.Y
);
}
static void DirectionalBlurKernel(Index2D index,
ArrayView2D<Vector3, Stride2D.DenseX> image,
ArrayView2D<Vector3, Stride2D.DenseX> mask,
ArrayView2D<Vector3, Stride2D.DenseX> output,
float radius, float step, float sigma,
int width, int height
) {
var x = index.X;
var y = index.Y;
var value = image[x, y];
var maskValue = mask[x, y];
if (maskValue.X == 0f) {
output[x, y] = value;
return;
}
var gradient = Vector2.Zero;
// calculate the gradient
for (int i = -1; i <= 1; i++) {
if (x + i < 0 || x + i >= width || y + i < 0 || y + i >= height) continue;
gradient.X += i * image[x + i, y].X;
gradient.Y += i * image[x, y + i].X;
}
/*
output[x, y] = new Vector3(float.Abs((gradient.X * 0.5f) + 0.5f),
float.Abs((gradient.Y * 0.5f) + 0.5f), 0.5f);
return;
*/
if (gradient == Vector2.Zero) {
output[x, y] = value;
return;
}
gradient = Vector2.Normalize(gradient);
float sum = 0;
// now we follow the direction line and sample the image for length;
for (var l = -radius; l <= radius; l += step) {
var xOffset = (gradient.X * l);
var yOffset = (gradient.Y * l);
var xSample = x + xOffset;
var ySample = y + yOffset;
if (xSample < 0 || xSample >= width || ySample < 0 || ySample >= height) continue;
var sampleValue = SampleBilinear(image, xSample, ySample);
var weight = MathF.Exp(-(l * l) / (2f * sigma * sigma));
output[x, y] += sampleValue * weight;
sum += weight;
}
output[x, y] = output[x, y] / sum;
}
}

200
SDFMapCreator/SdfKernels.cs Normal file
View File

@@ -0,0 +1,200 @@
using System;
using System.Numerics;
using ILGPU;
using ILGPU.Runtime;
using ILGPU.Runtime.CPU;
using ILGPU.Runtime.Cuda;
using ILGPU.Runtime.OpenCL;
using ILGPU.Algorithms;
namespace SDFMapCreator;
public partial class SdfKernels {
Context gpuContext;
public SdfKernels() {
// Initialize the GPU context
gpuContext = Context.Create(builder => builder
.OpenCL()
.Cuda()
.CPU()
.Math(MathMode.Fast32BitOnly)
.EnableAlgorithms()
);
Console.WriteLine("Reading available accelerators (CUDA only)...");
foreach (var device in gpuContext.GetCudaDevices()) {
using var accelerator = device.CreateAccelerator(gpuContext);
Console.WriteLine($"{GetInfoString(accelerator)}");
}
Console.WriteLine("Reading available accelerators (OpenCL only)...");
foreach (var device in gpuContext.GetCLDevices()) {
using var accelerator = device.CreateAccelerator(gpuContext);
Console.WriteLine($"{GetInfoString(accelerator)}");
}
Console.WriteLine("Reading available accelerators (CPU only)...");
foreach (var device in gpuContext.GetCPUDevices()) {
using var accelerator = device.CreateAccelerator(gpuContext);
Console.WriteLine($"{GetInfoString(accelerator)}");
}
}
public void SelfMask(Vector3[,] input, out Vector3[,] mask) {
var dev = gpuContext.GetPreferredDevice(false);
var width = input.GetLength(0);
var height = input.GetLength(1);
mask = new Vector3[width, height];
using var accelerator = dev.CreateAccelerator(gpuContext);
using var buffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
buffer.CopyFromCPU(input);
using var maskBuffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
var selfMaskKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D, ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>>(SelfMaskKernel);
selfMaskKernel(new(width, height), buffer.View, maskBuffer.View);
accelerator.Synchronize();
mask = maskBuffer.GetAsArray2D();
}
public void ABMask(Vector3[,] A, Vector3[,] B, out Vector3[,] mask) {
var dev = gpuContext.GetPreferredDevice(false);
var width = A.GetLength(0);
var height = A.GetLength(1);
mask = new Vector3[width, height];
using var accelerator = dev.CreateAccelerator(gpuContext);
using var bufferA = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
bufferA.CopyFromCPU(A);
using var bufferB = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
bufferB.CopyFromCPU(B);
using var maskBuffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
var abMaskKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>>(ABMaskKernel);
abMaskKernel(new(width, height), bufferA.View, bufferB.View, maskBuffer.View);
accelerator.Synchronize();
mask = maskBuffer.GetAsArray2D();
}
public void EdgeDetect(Vector3[,] mask, out Vector3[,] edge) {
var dev = gpuContext.GetPreferredDevice(false);
var width = mask.GetLength(0);
var height = mask.GetLength(1);
edge = new Vector3[width, height];
using var accelerator = dev.CreateAccelerator(gpuContext);
using var buffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
buffer.CopyFromCPU(mask);
var edgeKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D,
ArrayView2D<Vector3, Stride2D.DenseX>, uint, uint>(EdgeKernel);
edgeKernel(new(width, height), buffer.View, (uint)width, (uint)height);
accelerator.Synchronize();
edge = buffer.GetAsArray2D();
}
public void Sdf(Vector2[] edges, int width, int height, out Vector3[,] sdf) {
var dev = gpuContext.GetPreferredDevice(false);
using var accelerator = dev.CreateAccelerator(gpuContext);
using var buffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
using var edgeBuffer = accelerator.Allocate1D<Vector2>(edges.Length);
edgeBuffer.CopyFromCPU(edges);
var sdfKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView1D<Vector2, Stride1D.Dense>,
int, int>(SdfKernel);
sdfKernel(new(width, height), buffer.View, edgeBuffer.View, width, height);
accelerator.Synchronize();
sdf = buffer.GetAsArray2D();
}
public void Gradient(Vector3[,] mask, Vector3[,] sdfa, Vector3[,] sdfb, out Vector3[,] gradient) {
var dev = gpuContext.GetPreferredDevice(false);
var width = mask.GetLength(0);
var height = mask.GetLength(1);
gradient = new Vector3[width, height];
using var accelerator = dev.CreateAccelerator(gpuContext);
using var bufferMask = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
bufferMask.CopyFromCPU(mask);
using var bufferSdfa = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
bufferSdfa.CopyFromCPU(sdfa);
using var bufferSdfb = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
bufferSdfb.CopyFromCPU(sdfb);
using var bufferGradient = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
var gradientKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>>(GradientKernel);
gradientKernel(new(width, height), bufferMask.View, bufferSdfa.View, bufferSdfb.View, bufferGradient.View);
accelerator.Synchronize();
gradient = bufferGradient.GetAsArray2D();
}
public void DirectionalBlur(Vector3[,] image, Vector3[,] mask, out Vector3[,] output, float radius = 3f,
float step = .5f, float sigma = 1f) {
var dev = gpuContext.GetPreferredDevice(false);
var width = image.GetLength(0);
var height = image.GetLength(1);
output = new Vector3[width, height];
using var accelerator = dev.CreateAccelerator(gpuContext);
using var imageBuffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
imageBuffer.CopyFromCPU(image);
using var maskBuffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
maskBuffer.CopyFromCPU(mask);
using var outputBuffer = accelerator.Allocate2DDenseX<Vector3>(new(width, height));
var blurKernel = accelerator.LoadAutoGroupedStreamKernel<Index2D,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>,
ArrayView2D<Vector3, Stride2D.DenseX>,
float, float, float, int, int>(DirectionalBlurKernel);
blurKernel(new(width, height), imageBuffer.View, maskBuffer.View, outputBuffer.View,
radius, step, sigma, width, height);
accelerator.Synchronize();
output = outputBuffer.GetAsArray2D();
}
static string GetInfoString(Accelerator a) {
var infoString = new StringWriter();
a.PrintInformation(infoString);
return infoString.ToString();
}
}

BIN
SDFMapCreator/images/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
SDFMapCreator/images/02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
SDFMapCreator/images/03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
SDFMapCreator/images/04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
SDFMapCreator/images/05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
SDFMapCreator/images/06.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
SDFMapCreator/images/07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
SDFMapCreator/images/08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
SDFMapCreator/spherecut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B