diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a655285..bc2803f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,7 +24,7 @@ jobs:
uses: microsoft/setup-msbuild@v2
- name: Restore dependencies
- run: dotnet restore "Simple Icon File Maker.sln"
+ run: msbuild "Simple Icon File Maker.sln" /t:Restore /p:Configuration=Release /p:Platform=x64
- name: Build solution
- run: dotnet build "Simple Icon File Maker.sln" --configuration Release --no-restore
+ run: msbuild "Simple Icon File Maker.sln" /p:Configuration=Release /p:Platform=x64
diff --git a/Simple Icon File Maker/Simple Icon File Maker (Package)/Package.appxmanifest b/Simple Icon File Maker/Simple Icon File Maker (Package)/Package.appxmanifest
index 8da3f77..17eaf78 100644
--- a/Simple Icon File Maker/Simple Icon File Maker (Package)/Package.appxmanifest
+++ b/Simple Icon File Maker/Simple Icon File Maker (Package)/Package.appxmanifest
@@ -1,26 +1,28 @@
-
+
+ xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
+ IgnorableNamespaces="uap rescap systemai">
+ Version="1.15.0.0" />
+
Simple Icon File Maker
JoeFinApps
Images\StoreLogo.png
-
-
-
-
+
+
+
+
@@ -57,11 +59,24 @@
Images\Image128.png
+
+
+
+ .png
+ .jpg
+ .jpeg
+ .bmp
+ .ico
+
+ Bitmap
+
+
+
-
+
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker (Package)/Simple Icon File Maker (Package).wapproj b/Simple Icon File Maker/Simple Icon File Maker (Package)/Simple Icon File Maker (Package).wapproj
index dfb8eb3..4d95bfc 100644
--- a/Simple Icon File Maker/Simple Icon File Maker (Package)/Simple Icon File Maker (Package).wapproj
+++ b/Simple Icon File Maker/Simple Icon File Maker (Package)/Simple Icon File Maker (Package).wapproj
@@ -36,7 +36,7 @@
7887a19f-b1cd-4106-a9aa-abaacfe770a9
- 10.0.22621.0
+ 10.0.26100.0
10.0.19041.0
10.0.19041.0
net9.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)
@@ -51,6 +51,8 @@
True
0
SHA256
+ false
+ false
true
@@ -120,10 +122,10 @@
-
+
build
-
+
build
diff --git a/Simple Icon File Maker/Simple Icon File Maker/App.xaml.cs b/Simple Icon File Maker/Simple Icon File Maker/App.xaml.cs
index b4a692b..286e168 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/App.xaml.cs
+++ b/Simple Icon File Maker/Simple Icon File Maker/App.xaml.cs
@@ -1,12 +1,17 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
+using Microsoft.Windows.AppLifecycle;
using Simple_Icon_File_Maker.Activation;
using Simple_Icon_File_Maker.Contracts.Services;
using Simple_Icon_File_Maker.Models;
using Simple_Icon_File_Maker.Services;
using Simple_Icon_File_Maker.ViewModels;
using Simple_Icon_File_Maker.Views;
+using Windows.ApplicationModel.DataTransfer;
+using Windows.ApplicationModel.DataTransfer.ShareTarget;
+using Windows.Storage;
+using Windows.Storage.Streams;
namespace Simple_Icon_File_Maker;
@@ -37,6 +42,8 @@ public static T GetService()
public static UIElement? AppTitlebar { get; set; }
+ public static string? SharedImagePath { get; set; }
+
public App()
{
InitializeComponent();
@@ -93,8 +100,63 @@ private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledEx
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
+
+ var activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
+ if (activatedEventArgs.Kind == ExtendedActivationKind.ShareTarget)
+ {
+ await HandleShareTargetActivationAsync(activatedEventArgs);
+ }
+
await App.GetService().ActivateAsync(args);
}
+ private static async Task HandleShareTargetActivationAsync(AppActivationArguments activatedEventArgs)
+ {
+ if (activatedEventArgs.Data is Windows.ApplicationModel.Activation.IShareTargetActivatedEventArgs shareArgs)
+ {
+ ShareOperation shareOperation = shareArgs.ShareOperation;
+ shareOperation.ReportStarted();
+
+ try
+ {
+ if (shareOperation.Data.Contains(StandardDataFormats.StorageItems))
+ {
+ IReadOnlyList items = await shareOperation.Data.GetStorageItemsAsync();
+ foreach (IStorageItem item in items)
+ {
+ if (item is StorageFile file && Constants.FileTypes.SupportedImageFormats.Contains(file.FileType, StringComparer.OrdinalIgnoreCase))
+ {
+ StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
+ StorageFile copiedFile = await file.CopyAsync(tempFolder, file.Name, NameCollisionOption.GenerateUniqueName);
+ SharedImagePath = copiedFile.Path;
+ break;
+ }
+ }
+ }
+ else if (shareOperation.Data.Contains(StandardDataFormats.Bitmap))
+ {
+ var bitmapRef = await shareOperation.Data.GetBitmapAsync();
+ var stream = await bitmapRef.OpenReadAsync();
+
+ StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
+ StorageFile tempFile = await tempFolder.CreateFileAsync("shared_image.png", CreationCollisionOption.GenerateUniqueName);
+
+ using (var outputStream = await tempFile.OpenAsync(FileAccessMode.ReadWrite))
+ {
+ await RandomAccessStream.CopyAsync(stream, outputStream);
+ }
+
+ SharedImagePath = tempFile.Path;
+ }
+ }
+ catch (Exception)
+ {
+ // If share handling fails, continue with normal launch
+ }
+
+ shareOperation.ReportCompleted();
+ }
+ }
+
public static string[]? cliArgs { get; } = Environment.GetCommandLineArgs();
-}
+}
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/FodyWeavers.xml b/Simple Icon File Maker/Simple Icon File Maker/FodyWeavers.xml
deleted file mode 100644
index d5abfed..0000000
--- a/Simple Icon File Maker/Simple Icon File Maker/FodyWeavers.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs b/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs
new file mode 100644
index 0000000..994c091
--- /dev/null
+++ b/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs
@@ -0,0 +1,104 @@
+using Microsoft.Windows.AI;
+using Microsoft.Windows.AI.Imaging;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Graphics;
+using Windows.Graphics.Imaging;
+using Windows.Storage;
+using Windows.Storage.Streams;
+
+namespace Simple_Icon_File_Maker.Helpers;
+
+public static class BackgroundRemoverHelper
+{
+ public static async Task IsAvailableAsync()
+ {
+ try
+ {
+ AIFeatureReadyState readyState = ImageObjectExtractor.GetReadyState();
+
+ if (readyState == AIFeatureReadyState.Ready)
+ return true;
+
+ if (readyState == AIFeatureReadyState.NotReady)
+ {
+ var result = await ImageObjectExtractor.EnsureReadyAsync();
+ return result.Status == AIFeatureReadyResultState.Success;
+ }
+
+ // NotSupportedOnCurrentSystem or DisabledByUser
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public static async Task RemoveBackgroundAsync(string imagePath)
+ {
+ StorageFile inputFile = await StorageFile.GetFileFromPathAsync(imagePath);
+ SoftwareBitmap sourceBitmap;
+
+ using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
+ {
+ BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
+ sourceBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
+ }
+
+ using ImageObjectExtractor extractor = await ImageObjectExtractor.CreateWithSoftwareBitmapAsync(sourceBitmap);
+
+ // Hint with the entire image rect as the region of interest
+ ImageObjectExtractorHint hint = new(
+ includeRects: [new RectInt32(0, 0, sourceBitmap.PixelWidth, sourceBitmap.PixelHeight)],
+ includePoints: [],
+ excludePoints: []);
+
+ SoftwareBitmap mask = extractor.GetSoftwareBitmapObjectMask(hint);
+
+ SoftwareBitmap resultBitmap = ApplyMaskAsAlpha(sourceBitmap, mask);
+
+ StorageFolder cacheFolder = ApplicationData.Current.LocalCacheFolder;
+ string fileName = Path.GetFileNameWithoutExtension(imagePath);
+ string outputFileName = $"{fileName}_nobg.png";
+ StorageFile outputFile = await cacheFolder.CreateFileAsync(outputFileName, CreationCollisionOption.ReplaceExisting);
+
+ using (IRandomAccessStream outputStream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
+ {
+ BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, outputStream);
+ encoder.SetSoftwareBitmap(resultBitmap);
+ await encoder.FlushAsync();
+ }
+
+ return outputFile.Path;
+ }
+
+ private static SoftwareBitmap ApplyMaskAsAlpha(SoftwareBitmap original, SoftwareBitmap mask)
+ {
+ int width = original.PixelWidth;
+ int height = original.PixelHeight;
+
+ SoftwareBitmap gray = mask.BitmapPixelFormat == BitmapPixelFormat.Gray8
+ ? mask
+ : SoftwareBitmap.Convert(mask, BitmapPixelFormat.Gray8);
+
+ byte[] originalPixels = new byte[4 * width * height];
+ byte[] maskPixels = new byte[width * height];
+ original.CopyToBuffer(originalPixels.AsBuffer());
+ gray.CopyToBuffer(maskPixels.AsBuffer());
+
+ byte[] resultPixels = new byte[4 * width * height];
+ for (int i = 0; i < maskPixels.Length; i++)
+ {
+ int px = i * 4;
+ int m = 255 - maskPixels[i];
+ resultPixels[px + 0] = (byte)(originalPixels[px + 0] * m / 255);
+ resultPixels[px + 1] = (byte)(originalPixels[px + 1] * m / 255);
+ resultPixels[px + 2] = (byte)(originalPixels[px + 2] * m / 255);
+ resultPixels[px + 3] = (byte)(originalPixels[px + 3] * m / 255);
+ }
+
+ SoftwareBitmap result = new(BitmapPixelFormat.Bgra8, width, height, BitmapAlphaMode.Premultiplied);
+ result.CopyFromBuffer(resultPixels.AsBuffer());
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Helpers/FilePickerHelper.cs b/Simple Icon File Maker/Simple Icon File Maker/Helpers/FilePickerHelper.cs
index c6bba32..53b3eac 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/Helpers/FilePickerHelper.cs
+++ b/Simple Icon File Maker/Simple Icon File Maker/Helpers/FilePickerHelper.cs
@@ -17,6 +17,8 @@ public static async Task TrySetSuggestedFolderFromSourceImage(FileSavePicker sav
if (File.Exists(imagePath))
{
StorageFile sourceFile = await StorageFile.GetFileFromPathAsync(imagePath);
+ string name = Path.GetFileNameWithoutExtension(imagePath);
+ await sourceFile.RenameAsync(name);
savePicker.SuggestedSaveFile = sourceFile;
}
}
@@ -25,4 +27,4 @@ public static async Task TrySetSuggestedFolderFromSourceImage(FileSavePicker sav
// If file access fails, fall back to default picker behavior
}
}
-}
+}
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Models/IconSize.cs b/Simple Icon File Maker/Simple Icon File Maker/Models/IconSize.cs
index a709b99..a650808 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/Models/IconSize.cs
+++ b/Simple Icon File Maker/Simple Icon File Maker/Models/IconSize.cs
@@ -1,20 +1,26 @@
-using System.ComponentModel;
+using CommunityToolkit.Mvvm.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Simple_Icon_File_Maker.Models;
[DebuggerDisplay("SideLength = {SideLength}, IsSelected = {IsSelected}")]
-public partial class IconSize : INotifyPropertyChanged, IEquatable
+public partial class IconSize : ObservableObject, IEquatable
{
- public int SideLength { get; set; }
- public bool IsSelected { get; set; } = true;
+ [ObservableProperty]
+ public partial int SideLength { get; set; }
- public bool IsEnabled { get; set; } = true;
+ [ObservableProperty]
+ public partial bool IsSelected { get; set; } = true;
- public bool IsHidden { get; set; } = false;
+ [ObservableProperty]
+ public partial bool IsEnabled { get; set; } = true;
- public int Order { get; set; } = 0;
+ [ObservableProperty]
+ public partial bool IsHidden { get; set; } = false;
+
+ [ObservableProperty]
+ public partial int Order { get; set; } = 0;
public string Tooltip => $"{SideLength} x {SideLength}";
@@ -53,10 +59,6 @@ public IconSize(IconSize iconSize)
Order = iconSize.Order;
}
-#pragma warning disable CS0067 // The event 'IconSize.PropertyChanged' is never used
- public event PropertyChangedEventHandler? PropertyChanged;
-#pragma warning restore CS0067 // The event 'IconSize.PropertyChanged' is never used
-
public static IconSize[] GetAllSizes()
{
return
@@ -165,4 +167,4 @@ public int GetHashCode([DisallowNull] IconSize obj)
{
return obj.GetHashCode();
}
-}
+}
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj b/Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj
index 08b1c8c..04d3efe 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj
+++ b/Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj
@@ -1,8 +1,8 @@
-
+
WinExe
- net9.0-windows10.0.22621.0
- 10.0.22621.38
+ net9.0-windows10.0.26100.0
+ 10.0.26100.38
10.0.19041.0
10.0.19041.0
Simple_Icon_File_Maker
@@ -13,9 +13,9 @@
False
False
SimpleIconMaker.ico
- Joseph Finney 2024
+ Joseph Finney 2026
Image128.png
- enable
+ enable
enable
true
false
@@ -31,6 +31,7 @@
+
@@ -39,15 +40,14 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
@@ -81,6 +81,9 @@
$(DefaultXamlRuntime)
+
+ $(DefaultXamlRuntime)
+
diff --git a/Simple Icon File Maker/Simple Icon File Maker/ViewModels/MainViewModel.cs b/Simple Icon File Maker/Simple Icon File Maker/ViewModels/MainViewModel.cs
index 1935e9e..ab962c9 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/ViewModels/MainViewModel.cs
+++ b/Simple Icon File Maker/Simple Icon File Maker/ViewModels/MainViewModel.cs
@@ -1,4 +1,3 @@
-using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ImageMagick;
using Microsoft.UI.Xaml;
@@ -152,8 +151,14 @@ public async void OnNavigatedTo(object parameter)
ShowUpgradeToProButton = !_storeService.OwnsPro;
+ // Load shared image path from share target activation
+ if (!string.IsNullOrEmpty(App.SharedImagePath))
+ {
+ ImagePath = App.SharedImagePath;
+ App.SharedImagePath = null;
+ }
// Load CLI args if present
- if (App.cliArgs?.Length > 1)
+ else if (App.cliArgs?.Length > 1)
{
ImagePath = App.cliArgs[1];
}
@@ -605,6 +610,38 @@ public async Task ApplyInvert()
}
}
+ [RelayCommand]
+ public async Task RemoveBackground()
+ {
+ if (string.IsNullOrWhiteSpace(ImagePath))
+ return;
+
+ try
+ {
+ RemoveBackgroundDialog dialog = new(ImagePath);
+ await NavigationService.ShowModal(dialog);
+
+ if (dialog.ResultImagePath is not null)
+ {
+ ImageMagick.MagickImage resultImage = new(dialog.ResultImagePath);
+ if (MainImage != null)
+ MainImage.Source = resultImage.ToImageSource();
+
+ MagickImageUndoRedoItem undoRedoItem = new(MainImage!, ImagePath, dialog.ResultImagePath);
+ _undoRedo.AddUndo(undoRedoItem);
+ UpdateUndoRedoState();
+
+ ImagePath = dialog.ResultImagePath;
+ await RefreshPreviews();
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to remove background: {ex.Message}");
+ ShowError($"Failed to remove background: {ex.Message}");
+ }
+ }
+
[RelayCommand]
public async Task Undo()
{
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Views/AboutPage.xaml b/Simple Icon File Maker/Simple Icon File Maker/Views/AboutPage.xaml
index 6af0196..847b30a 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/Views/AboutPage.xaml
+++ b/Simple Icon File Maker/Simple Icon File Maker/Views/AboutPage.xaml
@@ -1,4 +1,4 @@
-
@@ -165,7 +165,7 @@
+ NavigateUri="https://github.com/microsoft/microsoft-ui-xaml">
WinUI 3
-
+
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Views/MainPage.xaml b/Simple Icon File Maker/Simple Icon File Maker/Views/MainPage.xaml
index 84b354d..8b740c7 100644
--- a/Simple Icon File Maker/Simple Icon File Maker/Views/MainPage.xaml
+++ b/Simple Icon File Maker/Simple Icon File Maker/Views/MainPage.xaml
@@ -1,4 +1,3 @@
-
+
+
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml
new file mode 100644
index 0000000..47b413e
--- /dev/null
+++ b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml.cs b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml.cs
new file mode 100644
index 0000000..2a62eda
--- /dev/null
+++ b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml.cs
@@ -0,0 +1,63 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Imaging;
+using Simple_Icon_File_Maker.Helpers;
+
+namespace Simple_Icon_File_Maker;
+
+public sealed partial class RemoveBackgroundDialog : ContentDialog
+{
+ public string? ResultImagePath { get; private set; }
+
+ private readonly string _imagePath;
+ private string? _pendingResultPath;
+
+ public RemoveBackgroundDialog(string imagePath)
+ {
+ InitializeComponent();
+ _imagePath = imagePath;
+ PrimaryButtonClick += OnPrimaryButtonClick;
+ }
+
+ private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
+ {
+ ResultImagePath = _pendingResultPath;
+ }
+
+ private async void ContentDialog_Loaded(object sender, RoutedEventArgs e)
+ {
+ BeforeImage.Source = new BitmapImage(new Uri(_imagePath));
+
+ bool isAvailable = await BackgroundRemoverHelper.IsAvailableAsync();
+ if (!isAvailable)
+ {
+ StatusInfoBar.Title = "Not Available";
+ StatusInfoBar.Message = "The AI background removal model is not available on this device. This feature requires a Copilot+ PC with the latest Windows updates.";
+ StatusInfoBar.Severity = InfoBarSeverity.Warning;
+ StatusInfoBar.IsOpen = true;
+ ProcessingRing.IsActive = false;
+ return;
+ }
+
+ try
+ {
+ string resultPath = await BackgroundRemoverHelper.RemoveBackgroundAsync(_imagePath);
+ _pendingResultPath = resultPath;
+
+ AfterImage.Source = new BitmapImage(new Uri(resultPath));
+
+ IsPrimaryButtonEnabled = true;
+ }
+ catch (Exception ex)
+ {
+ StatusInfoBar.Title = "Error";
+ StatusInfoBar.Message = $"Failed to remove background: {ex.Message}";
+ StatusInfoBar.Severity = InfoBarSeverity.Error;
+ StatusInfoBar.IsOpen = true;
+ }
+ finally
+ {
+ ProcessingRing.IsActive = false;
+ }
+ }
+}
\ No newline at end of file