Compare commits

..

66 Commits

Author SHA1 Message Date
Samuele Lorefice
769cb8fd5f Removed last instances of direct Console.Write calls, updated the Linewalker Library. 2025-04-28 16:18:29 +02:00
Samuele Lorefice
359ae02e19 Added ImageSharp license to opensource licenses. 2025-04-24 15:16:42 +02:00
Samuele Lorefice
cdbcabd6a1 Added OpenSource licenses file to be output at build for ILGpu 2025-04-23 17:39:20 +02:00
Samuele Lorefice
780baa6e7a Added License 2025-04-23 16:58:13 +02:00
Samuele Lorefice
eaf1ced1c3 Added tiny logging library. Revised logging. 2025-04-09 03:28:18 +02:00
Samuele Lorefice
93002270d5 Changed blur iterations to reuse buffers. Huge memory savings + speedup 2025-04-08 21:39:06 +02:00
Samuele Lorefice
3e90df9e6e Removed unused properties from record 2025-04-08 20:03:05 +02:00
Samuele Lorefice
687de13d6c Added logline specifying what accelerator has been bound 2025-04-08 19:19:48 +02:00
Samuele Lorefice
2609d231e7 Added missing copy images script run configs 2025-04-08 19:11:43 +02:00
Samuele Lorefice
964eb02adb Added DeviceType switch, removed copy script from run configs (rider bug prevents execution), added CUDA and OpenCL run configs. 2025-04-08 18:53:52 +02:00
Samuele Lorefice
8992a6c33d Added windows version of shell script to build configs 2025-04-07 17:46:17 +02:00
Samuele Lorefice
ebfb083099 Rider updating files when closing is annoying 2025-04-07 10:46:44 +02:00
Samuele Lorefice
de46c11510 fixed debug images being output without debug flag.
Explicitly added quotes ("") to readme command example
2025-04-07 10:40:15 +02:00
Samuele Lorefice
f37c7039e5 Added readme 2025-04-07 10:04:15 +02:00
Samuele Lorefice
1defbd66ff Final cleanup and added last run configs 2025-04-07 09:55:17 +02:00
Samuele Lorefice
f342a2158b Command line argument parsing added, general logging cleanup. Buildchain improvements 2025-04-07 09:45:34 +02:00
Samuele Lorefice
1de6ee2127 Started pre-release cleanup 2025-04-07 08:24:54 +02:00
Samuele Lorefice
f1d1749c40 Merge branch 'ILGpu' 2025-04-07 07:59:37 +02:00
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
07c00117f1 Added directional blur for final image 2025-04-02 19:45:20 +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
Samuele Lorefice
809068e270 Gradient + Transitions 2025-03-26 06:42:46 +01:00
Samuele Lorefice
e3dbb82587 Removed extra reference in record 2025-03-26 06:38:43 +01:00
43 changed files with 5488 additions and 225 deletions

4012
.editorconfig Normal file

File diff suppressed because it is too large Load Diff

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ obj/
/packages/
riderModule.iml
/_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,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LocationsWithSilentDeleteHolder">
<option name="locations">
<list>
<option value="$PROJECT_DIR$/SDFMapCreator/publish/win-x64" />
<option value="$PROJECT_DIR$/SDFMapCreator/publish/linux-x64" />
</list>
</option>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Copy Images Psh" type="PowerShellRunType" factoryName="PowerShell" activateToolWindowBeforeRun="false" scriptUrl="I:\SDFMapCreator\SDFMapCreator\copyImages.ps1" workingDirectory="I:\SDFMapCreator\SDFMapCreator" executablePath="C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe">
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Copy Images Sh" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="false" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/SDFMapCreator/copyImages.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</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,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run SDF Tool [8 pic CUDA]" type="DotNetProject" factoryName=".NET Project" focusToolWindowBeforeRun="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0/SDFMapTool.exe" />
<option name="PROGRAM_PARAMETERS" value="-D ./images -o &quot;./sdf.png&quot; -i &quot;01.png;02.png;03.png;04.png;05.png;06.png;07.png;08.png&quot; --device CUDA" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net8.0" />
<method v="2">
<option name="Build" default="false" projectName="SDFMapCreator" projectPath="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run SDF Tool [8 pic OpenCL]" type="DotNetProject" factoryName=".NET Project" focusToolWindowBeforeRun="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0/SDFMapTool.exe" />
<option name="PROGRAM_PARAMETERS" value="-D ./images -o &quot;./sdf.png&quot; -i &quot;01.png;02.png;03.png;04.png;05.png;06.png;07.png;08.png&quot; --device OpenCL" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net8.0" />
<method v="2">
<option name="Build" default="false" projectName="SDFMapCreator" projectPath="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run SDF Tool [Debug, 8 pic]" type="DotNetProject" factoryName=".NET Project" focusToolWindowBeforeRun="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0/SDFMapTool.exe" />
<option name="PROGRAM_PARAMETERS" value="-d -D &quot;./images&quot; -o &quot;./sdf.png&quot; -i &quot;01.png;02.png;03.png;04.png;05.png;06.png;07.png;08.png&quot;" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/SDFMapCreator/bin/Debug/net8.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net8.0" />
<method v="2">
<option name="Build" default="false" projectName="SDFMapCreator" projectPath="$PROJECT_DIR$/SDFMapCreator/SDFMapCreator.csproj" />
</method>
</configuration>
</component>

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

41
Readme.md Normal file
View File

@@ -0,0 +1,41 @@
# SDF Tool
*A signed distance fields-based tool to generate shadow transition maps*
## Key features
- float16 png output for reduced banding
- Automatic CUDA/OpenCL GPU Acceleration for most of the process
- *nix style command line interface
## How to use
From command line: `SDFMapTool -D <directory_with_images> -i "<image1.png;image2.png;...>" -o <output_file_path.png> ...`
Available options:
```terminal
-d, --debug Enable debug mode.
-D, --imgDirectory Required. Input Images directory.
-i, --images Required. Images file names separated by ';' and in the correct order.
-o, --output Required. Output file path.
-b, --blur How many blur iterations to run.
-r, --radius Blur radius.
-s, --step Blur step size.
--sigma Blur sigma value (weighting).
--device Device to use for computation. (CPU, OpenCL, CUDA)
--help Display this help screen.
--version Display version information.
```

View File

@@ -12,5 +12,6 @@ Global
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Debug|Any CPU.Build.0 = Debug|Any CPU
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Release|Any CPU.ActiveCfg = Release|Any CPU
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Release|Any CPU.Build.0 = Release|Any CPU
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
EndGlobalSection
EndGlobal

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,18 @@
<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_003ADanywyf_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Ftmp_003FJetBrainsPerUserTemp_002D1000_002D1_003FSandboxFiles_003FLiqequv_003FDanywyf_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_003ALogger_002Ecs_002Fl_003AC_0021_003FUsers_003Fairon_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F70a76ec6534f41dfb670b3e0b4505f5f2600_003F8a_003F2e6ec04f_003FLogger_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:Int64 x:Key="/Default/Dpa/Thresholds/=AllocationLoh/@EntryIndexedValue">52428800</s:Int64>
<s:Int64 x:Key="/Default/Dpa/Thresholds/=AllocationTopSoh/@EntryIndexedValue">26214400</s:Int64>
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

204
SDFMapCreator/ImageUtil.cs Normal file
View File

@@ -0,0 +1,204 @@
/*
Copyright 2025 Railgun Entertainment AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.Numerics;
using LineWalker;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace SDFMapCreator;
public static class ImageUtil {
static Logger logger = Logger.GetInstance();
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.")
};
logger.Log($"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;
}
}
}
});
logger.Log($"Writing image {path}...Done", true);
image.Save(path);
}
static void ImageData(SixLabors.ImageSharp.Image image1, string path) {
logger.Log(
$"""
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

@@ -0,0 +1,81 @@
********************************************************************************
ILGPU License
********************************************************************************
University of Illinois/NCSA Open Source License
Copyright (c) 2016-2025 ILGPU Project
All rights reserved.
Developed by: Marcel Koester (m4rs@m4rs.net)
www.ilgpu.net
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the documentation
and/or other materials provided with the distribution.
* Neither the names of ILGPU, Marcel Koester, nor the names of its
contributors may be used to endorse or promote products derived from this
Software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
------------------------------------------------------------------------------------------------------------------------
Six Labors Split License
Version 1.0, June 2022
Copyright (c) Six Labors
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source
code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a
copyright notice that is included in or attached to the work.
"Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You.
"Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party
dependency unrelated to Six Labors.
2. License
Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a
Six Labors Commercial Use License.
Licenses are granted based upon You meeting the qualified criteria as stated. Once granted,
You must reference the granted license only in all documentation.
Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if.
- You are consuming the Work in for use in software licensed under an Open Source or Source Available license.
- You are consuming the Work as a Transitive Package Dependency.
- You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with
less than 1M USD annual gross revenue.
- You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization
or Registered Charity.
For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License
which may be purchased by visiting https://sixlabors.com/pricing/.

View File

@@ -1,254 +1,285 @@
using System.Diagnostics;
/*
Copyright 2025 Railgun Entertainment AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageMagick;
using CommandLine;
using LineWalker;
public struct float2(float x, float y) {
public float x = x, y = y;
namespace SDFMapCreator;
public enum DeviceType {
CPU,
OpenCL,
CUDA
}
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 class Options {
[Option('d', "debug", Required = false, HelpText = "Enable debug mode.")]
public bool Debug { get; set; } = false;
[Option('D', "imgDirectory", Required = true, HelpText = "Input Images directory.")]
public string ImgDirectory { get; set; } = "images";
[Option('i', "images", Required = true, HelpText = "Images file names separated by ';' and in the correct order.")]
public string Images { get; set; } = "";
[Option('o', "output", Required = true, HelpText = "Output file path.")]
public string OutputFile { get; set; } = "output.png";
[Option('b', "blur", Required = false, HelpText = "How many blur iterations to run.")]
public int BlurIterations { get; set; } = 10;
[Option('r', "radius", Required = false, HelpText = "Blur radius.")]
public float BlurRadius { get; set; } = 100f;
[Option('s', "step", Required = false, HelpText = "Blur step size.")]
public float BlurStep { get; set; } = 0.5f;
[Option("sigma", Required = false, HelpText = "Blur sigma value (weighting).")]
public float BlurSigma { get; set; } = 1f;
[Option("device", Required = false, HelpText = "Device to use for computation. (CPU, OpenCL, CUDA)")]
public DeviceType? Device { get; set; } = null;
}
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 static class Program {
#region Magic Values
const float MAX = 1f;
const float MIN = 0f;
static readonly int MAX_THREADS = Environment.ProcessorCount - 2;
static readonly ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = MAX_THREADS };
static char PSep => Path.DirectorySeparatorChar;
#endregion
public record ImageData(MagickImage Image, float3[,] Pixels, List<float2> Edges);
public record MaskData(float3[,] Mask, ImageData A, ImageData B, List<float2> Edges);
static Logger logger = Logger.GetInstance();
public record SDFData(float3[,] SDF);
static Options options = null!;
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 = true;
static List<ImageData> Images = new();
static List<MaskData> Masks = new();
static List<Image> Images = new();
static List<MaskData> ImageMasks = new();
static List<TransitionMaskData> TransitionMasks = new();
static List<SDFData> SDFs = 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 SdfKernels kernels;
static uint width;
static uint height;
public static void Main(string[] args) {
Console.WriteLine("Reading images...");
//foreach image in arguments load the image
LoadImage("01.png");
LoadImage("02.png");
LoadImage("03.png");
LoadImage("04.png");
LoadImage("05.png");
LoadImage("06.png");
LoadImage("07.png");
LoadImage("08.png");
var parser = Parser.Default;
parser.ParseArguments<Options>(args)
.WithParsed(o => options = o)
.WithNotParsed(_ => {
Environment.Exit(1);
});
if(options.Device != null) kernels = new (options.Device.Value);
else kernels = new();
string debugPath = $"{Environment.CurrentDirectory}{PSep}Debug";
if (options.Debug) {
if (!Directory.Exists(debugPath)) Directory.CreateDirectory(debugPath);
logger.Log("Debug mode enabled.", LogLevel.Debug);
}
logger.Log("Reading images...");
var imageNames = options.Images.Split(';');
for (var i = 0; i < imageNames.GetLength(0); i++) {
string imgPath = $"{options.ImgDirectory}{PSep}{imageNames[i]}";
logger.Log($"Reading image {imgPath}", updatePrevious:true);
var pixels = ImageUtil.LoadImage<Vector3>(imgPath);
Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1)));
}
//LoadImage("1.png");
//LoadImage("2.png");
//check if all the images in Images are the same resolution
if (Images.Select(img => (img.Image.Width, img.Image.Height)).Distinct().Count() > 1) {
Console.WriteLine("Error: Not all images have the same resolution.");
if (Images.Select(img => (img.Width, img.Height)).Distinct().Count() > 1) {
logger.Log("Error: Not all images have the same resolution.", LogLevel.Critical);
Environment.Exit(1);
}
Console.WriteLine("Creating masks...");
//for each image pair, create a mask
for (int i = 0; i < Images.Count - 1; i++) {
var mask = GetABMask(Images[i].Pixels, Images[i + 1].Pixels, Images[i].Image.Width, Images[i].Image.Height);
Masks.Add(new(mask, Images[i], Images[i + 1], new()));
width = (uint)Images[0].Width;
height = (uint)Images[0].Height;
// sum all images together
var sumPath = $"{debugPath}{PSep}Sum";
if(options.Debug) Images[0].Pixels.SaveImage($"{sumPath}0.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);
}
}
if(options.Debug)Images[i].Pixels.SaveImage($"{sumPath}{i}.png");
}
logger.Log("Creating masks...");
for (var i = 0; i < Images.Count; i++) { //for each image pair, create a mask
logger.Log($"Creating mask {i}...", updatePrevious:true);
var selfMask = SelfMask(Images[i].Pixels);
ImageMasks.Add(new(selfMask, Images[i], new()));
if (options.Debug) selfMask.SaveImage($"{debugPath}{PSep}selfMask{i}.png");
if (i >= Images.Count - 1) continue;
var mask = GetABMask(Images[i].Pixels, Images[i + 1].Pixels);
TransitionMasks.Add(new(mask));
}
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);
}
logger.Log("Edge detecting masks...");
foreach (var mask in ImageMasks) {
EdgeDetect(mask);
}
Console.WriteLine("Creating SDFs...");
for (var i = 0; i < Masks.Count; i++) {
var mask = Masks[i];
var sdf = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
if (options.Debug)
for (var i = 0; i < TransitionMasks.Count; i++)
ImageMasks[i].Mask.SaveImage($"{debugPath}{PSep}mask{i}.png");
logger.Log("Creating SDFs...");
for (var i = 0; i < ImageMasks.Count; i++) {
var mask = ImageMasks[i];
SDFs.Add(SDF(mask));
sdf.GetPixels().SetPixels(SDFs[i].SDF.ToFloatArray());
sdf.Write($"sdf{i}.png", MagickFormat.Png48);
if (options.Debug) SDFs[i].SDF.SaveImage($"{debugPath}{PSep}sdf{i}.png");
}
Console.WriteLine("Done!");
logger.Log("Creating gradients...");
for (var i = 0; i < TransitionMasks.Count; i++) {
var gradientData = Gradient(TransitionMasks[i], SDFs[i], SDFs[i + 1]);
Gradients.Add(gradientData);
if (options.Debug) gradientData.SaveImage($"{debugPath}{PSep}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];
logger.Log($"Applying gradient {i}..., Step: {currStep:F2} -> Next: {(currStep + stepIncrement):F2}");
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;
}
if(options.Debug) finalImage.SaveImage($"{debugPath}{options.OutputFile}");
// apply directional blur
var iterations = options.BlurIterations;
var radius = options.BlurRadius;
var step = options.BlurStep;
var sigma = options.BlurSigma;
var totalMask = SelfMask(Images[^1].Pixels);
if(options.Debug) totalMask.SaveImage($"{debugPath}{PSep}TotalMask.png");
finalImage = DirectionalBlur(finalImage, totalMask, iterations, radius, step, sigma);
finalImage.SaveImage(options.OutputFile);
logger.Log("Done!");
Logger.Shutdown();
}
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");
string logString = $"Edge detecting mask {ImageMasks.IndexOf(maskData)}..." +
Environment.NewLine + $"Edge pixels: {maskData.Edges.Count} | {width * height / (sw.ElapsedMilliseconds + 1)} pixels/s"+
Environment.NewLine + $"Time: {sw.Elapsed.TotalSeconds:F4}s";
logger.Log(logString, updatePrevious:true);
}
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;
static Vector3[,] Gradient(TransitionMaskData mask, SDFData sdfA, SDFData sdfB) {
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);
//skip all pixels we don't care about
if(mask.Mask[x, y].r == 0) {
temp[x, y] = new(MIN);
return;
}
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");
}
});
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) => {
//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));
});
Console.WriteLine($"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine("AbsMax: " + AbsMax);
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) {
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);
});
kernels.Gradient(mask.Mask, sdfA.SDF, sdfB.SDF, out var temp);
logger.Log($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / (float)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;
static SDFData SDF(MaskData mask) {
var sw = new Stopwatch();
sw.Start();
kernels.Sdf(mask.Edges.ToArray(), (int)width, (int)height, out var temp);
string logString = $"SDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / sw.Elapsed.TotalSeconds:N0} pixels/s)";
sw.Restart();
var absMax = 0f;
Parallel.ForEach(temp.Cast<Vector3>(), pixel => {
var localMax = pixel.X;
Interlocked.Exchange(ref absMax, MathF.Max(absMax, localMax));
});
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].X, 0, absMax, MIN, MAX));
});
sw.Stop();
logString += Environment.NewLine +
$"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height / sw.Elapsed.TotalSeconds:N0} pixels/s)";
logger.Log(logString, updatePrevious:true);
return new(temp);
}
static Vector3[,] DirectionalBlur(Vector3[,] image, Vector3[,] mask, int iterations, float radius = 3f, float step = .5f, float sigma = 1f) {
var sw = new Stopwatch();
sw.Start();
kernels.DirectionalBlur(image, mask, out var temp, iterations, radius, step, sigma);
logger.Log($"Directional Blur Time: {sw.Elapsed.TotalSeconds:N4}s ({width * height * iterations / sw.Elapsed.TotalSeconds:N0} pixels/s)");
return temp;
}
[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;
=> a * (1 - t) + b * t;
static Vector3 Lerp(Vector3 a, Vector3 b, float 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;
}

30
SDFMapCreator/Records.cs Normal file
View File

@@ -0,0 +1,30 @@
/*
Copyright 2025 Railgun Entertainment AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
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);

View File

@@ -5,22 +5,71 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SDFMapTool</AssemblyName>
<Company>Circle2Labs</Company>
<Product>SDFMapTool</Product>
<AssemblyVersion>1.0</AssemblyVersion>
<FileVersion>1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-HDRI-OpenMP-x64" Version="14.5.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="ILGPU" Version="1.5.2"/>
<PackageReference Include="ILGPU.Algorithms" Version="1.5.2"/>
<PackageReference Include="LineWalker" Version="0.1.2-alpha" />
<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="images\01.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\02.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\03.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\04.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\05.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\06.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\07.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\08.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherecut.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherecut32.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\sphereempty.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherefull.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherefull32.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherehalf.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\spherehalf32.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="OpenSourceLicenses.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,180 @@
/*
Copyright 2025 Railgun Entertainment AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
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;
}
if (gradient == Vector2.Zero) {
output[x, y] = value;
return;
}
//clean up the buffer in case
output[index] = new(0f, 0f, 0f);
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;
}
}

232
SDFMapCreator/SdfKernels.cs Normal file
View File

@@ -0,0 +1,232 @@
/*
Copyright 2025 Railgun Entertainment AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.Numerics;
using ILGPU;
using ILGPU.Runtime;
using ILGPU.Runtime.CPU;
using ILGPU.Runtime.Cuda;
using ILGPU.Runtime.OpenCL;
using LineWalker;
namespace SDFMapCreator;
public partial class SdfKernels {
Logger logger = Logger.GetInstance();
Context gpuContext;
Accelerator accelerator;
public SdfKernels() {
// Initialize the GPU context
gpuContext = Context.Create(builder => builder
.Cuda()
.OpenCL()
.CPU()
.Math(MathMode.Fast32BitOnly)
.EnableAlgorithms()
);
logger.Log("Reading available accelerators (CUDA only)...");
foreach (var device in gpuContext.GetCudaDevices()) {
accelerator = device.CreateAccelerator(gpuContext);
logger.Log($"Found accelerator: {accelerator.Name} ({accelerator.AcceleratorType})");
}
logger.Log("Reading available accelerators (OpenCL only)...");
foreach (var device in gpuContext.GetCLDevices()) {
accelerator = device.CreateAccelerator(gpuContext);
logger.Log($"Found accelerator: {accelerator.Name} ({accelerator.AcceleratorType})");
}
logger.Log("Reading available accelerators (CPU only)...");
foreach (var device in gpuContext.GetCPUDevices()) {
accelerator = device.CreateAccelerator(gpuContext);
logger.Log($"Found accelerator: {accelerator.Name} ({accelerator.AcceleratorType})");
}
accelerator = gpuContext.GetPreferredDevice(false).CreateAccelerator(gpuContext);
logger.Log($"Using accelerator: {accelerator.Name} ({accelerator.AcceleratorType})");
}
public SdfKernels(DeviceType device) {
logger.Log($"Looking up for {device.ToString()} accelerators...");
var builder = Context.Create();
switch (device) {
case DeviceType.CPU: builder.CPU(); break;
case DeviceType.CUDA: builder.Cuda(); break;
case DeviceType.OpenCL: builder.OpenCL(); break;
}
gpuContext = builder
.Math(MathMode.Fast32BitOnly)
.EnableAlgorithms()
.ToContext();
accelerator = gpuContext.GetPreferredDevice(false).CreateAccelerator(gpuContext);
logger.Log($"Using accelerator: {accelerator.Name} ({accelerator.AcceleratorType})");
}
public void SelfMask(Vector3[,] input, out Vector3[,] mask) {
var width = input.GetLength(0);
var height = input.GetLength(1);
mask = new Vector3[width, height];
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 width = A.GetLength(0);
var height = A.GetLength(1);
mask = new Vector3[width, height];
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 width = mask.GetLength(0);
var height = mask.GetLength(1);
edge = new Vector3[width, height];
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) {
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 width = mask.GetLength(0);
var height = mask.GetLength(1);
gradient = new Vector3[width, height];
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, int iterations, float radius = 3f,
float step = .5f, float sigma = 1f) {
var width = image.GetLength(0);
var height = image.GetLength(1);
output = new Vector3[width, height];
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);
var stream = accelerator.DefaultStream;
for (int i = 0; i < iterations; i++) {
if (i > 0) outputBuffer.CopyTo(stream, imageBuffer);
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();
}
~SdfKernels() {
accelerator?.Dispose();
gpuContext?.Dispose();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,29 @@
# Copyright 2025 Railgun Entertainment AS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
$imagesPath = "./images/*"
if (Test-Path "./bin/Debug/net8.0/")
{
Write-Output "Copying images to Debug folder"
Copy-Item -Path $imagesPath -Destination "./bin/Debug/net8.0/images/"
}
if(Test-Path "./bin/Release/net8.0/")
{
Write-Output "Copying images to Release folder"
Copy-Item -Path $imagesPath -Destination "./bin/Release/net8.0/images/"
}
return 0

View File

@@ -0,0 +1,23 @@
# Copyright 2025 Railgun Entertainment AS
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if [ -d ./bin/Debug/net8.0/ ]; then
echo "Copying images to bin/Debug"
cp -r ./images ./bin/Debug/net8.0/
fi
if [ -d ./bin/Release/net8.0/ ]; then
echo "Copying images to bin/Release"
cp -r ./images ./bin/Release/net8.0/
fi

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

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