From 502e7bd02033ee882fad0b057dcca4c9fd299ec7 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 16:33:35 -0600 Subject: [PATCH 1/9] Update NuGet package dependencies to latest versions --- .../Simple Icon File Maker (Package).wapproj | 4 ++-- .../Simple Icon File Maker.csproj | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) 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..e20ec2a 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 @@ -120,10 +120,10 @@ - + build - + build 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..9953037 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 @@ -39,14 +39,14 @@ - - - - + + + + - - - + + + From aedf076a7922f430b829a81e2491deb0093795e3 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 17:07:37 -0600 Subject: [PATCH 2/9] Removed Fody --- .../Simple Icon File Maker/FodyWeavers.xml | 3 --- .../Simple Icon File Maker/Models/IconSize.cs | 24 ++++++++++--------- .../Simple Icon File Maker.csproj | 1 - 3 files changed, 13 insertions(+), 15 deletions(-) delete mode 100644 Simple Icon File Maker/Simple Icon File Maker/FodyWeavers.xml 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/Models/IconSize.cs b/Simple Icon File Maker/Simple Icon File Maker/Models/IconSize.cs index a709b99..07b4369 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 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 9953037..3159d17 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 @@ -47,7 +47,6 @@ - From 58ecf0b43872a83b46cdf73ad3bf48870953e397 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 17:45:44 -0600 Subject: [PATCH 3/9] Rename source file before suggesting in save picker The source file is now renamed to its filename without extension before being set as the suggested save file, ensuring consistent naming in the file picker dialog. --- .../Simple Icon File Maker/Helpers/FilePickerHelper.cs | 2 ++ 1 file changed, 2 insertions(+) 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..bfe9695 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; } } From d0e99d51199ad68ce344c4b13f0d2e33f3eb8529 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 17:59:08 -0600 Subject: [PATCH 4/9] Add Windows Share Target support for image files Enable the app to receive images via the Windows Share UI by declaring share target support in the manifest and handling share activation. Shared images are copied to a temp folder and loaded automatically on launch. CLI argument loading is now secondary to share activation. --- .../Package.appxmanifest | 14 ++++- .../Simple Icon File Maker/App.xaml.cs | 62 +++++++++++++++++++ .../ViewModels/MainViewModel.cs | 8 ++- 3 files changed, 82 insertions(+), 2 deletions(-) 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..b54c293 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,4 +1,4 @@ - + Images\Image128.png + + + + .png + .jpg + .jpeg + .bmp + .ico + + Bitmap + + 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..f3c010a 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.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(); } 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..e9ad5db 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 @@ -152,8 +152,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]; } From 35ffd7cd6445588dc88e1d25366b8cb939ec882f Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 20:09:00 -0600 Subject: [PATCH 5/9] Add AI-powered background removal using Windows system AI Introduced "Remove Background (AI)" feature leveraging Windows on-device AI (ImageObjectExtractor) to extract the main object from images. Added BackgroundRemoverHelper for model availability checks and background removal logic. Implemented RemoveBackgroundDialog for before/after previews and user interaction. Updated MainViewModel and MainPage to integrate the new command and menu item. Increased Windows SDK/TargetPlatformVersion to 10.0.26100.0 and added systemAIModels capability in the manifest. Registered new dialog and updated project files for compatibility and consistency. --- .../Package.appxmanifest | 13 +- .../Simple Icon File Maker (Package).wapproj | 260 +++++++++--------- .../Helpers/BackgroundRemoverHelper.cs | 116 ++++++++ .../Simple Icon File Maker.csproj | 10 +- .../ViewModels/MainViewModel.cs | 32 +++ .../Views/MainPage.xaml | 5 + .../Views/RemoveBackgroundDialog.xaml | 80 ++++++ .../Views/RemoveBackgroundDialog.xaml.cs | 54 ++++ 8 files changed, 433 insertions(+), 137 deletions(-) create mode 100644 Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs create mode 100644 Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml create mode 100644 Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml.cs 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 b54c293..02dc6e6 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 @@ -4,23 +4,25 @@ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" - IgnorableNamespaces="uap rescap"> + xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10" + IgnorableNamespaces="uap rescap systemai"> + Simple Icon File Maker JoeFinApps Images\StoreLogo.png - - - - + + + + @@ -75,5 +77,6 @@ + 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 e20ec2a..20445a7 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 @@ -1,132 +1,134 @@ - - 15.0 - - - - Debug - x86 - - - Release - x86 - - - Debug - x64 - - - Release - x64 - - - Debug - arm64 - - - Release - arm64 - - - - $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ - Simple Icon File Maker\ - - - - 7887a19f-b1cd-4106-a9aa-abaacfe770a9 - 10.0.22621.0 - 10.0.19041.0 - 10.0.19041.0 - net9.0-windows$(TargetPlatformVersion);$(AssetTargetFallback) - en-US - false - ..\Simple Icon File Maker\Simple Icon File Maker.csproj - False - False - True - x86|x64|arm64 - x86|x64|arm64 - True - 0 - SHA256 - true - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - Properties\PublishProfiles\win-$(Platform).pubxml - - - - - build - - - build - - - - + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + arm64 + + + Release + arm64 + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + Simple Icon File Maker\ + + + + 7887a19f-b1cd-4106-a9aa-abaacfe770a9 + 10.0.26100.0 + 10.0.19041.0 + 10.0.19041.0 + net9.0-windows$(TargetPlatformVersion);$(AssetTargetFallback) + en-US + false + ..\Simple Icon File Maker\Simple Icon File Maker.csproj + False + False + True + x86|x64|arm64 + x86|x64|arm64 + True + 0 + SHA256 + false + false + true + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Properties\PublishProfiles\win-$(Platform).pubxml + + + + + build + + + build + + + + \ 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..2350fcb --- /dev/null +++ b/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs @@ -0,0 +1,116 @@ +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); + + // Use a rectangle covering the central ~90% of the image so the model + // identifies the dominant object rather than a single small element. + int marginX = sourceBitmap.PixelWidth / 20; + int marginY = sourceBitmap.PixelHeight / 20; + + RectInt32 rect = new() + { + X = marginX, + Y = marginY, + Width = sourceBitmap.PixelWidth - 2 * marginX, + Height = sourceBitmap.PixelHeight - 2 * marginY + }; + + ImageObjectExtractorHint hint = new( + includeRects: [rect], + includePoints: null, + excludePoints: null); + + 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 = 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; + } +} 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 3159d17..1df1c05 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 @@ -15,7 +15,7 @@ SimpleIconMaker.ico Joseph Finney 2024 Image128.png - enable + enable enable true false @@ -31,6 +31,7 @@ + @@ -80,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 e9ad5db..091cc94 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 @@ -611,6 +611,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/MainPage.xaml b/Simple Icon File Maker/Simple Icon File Maker/Views/MainPage.xaml index 84b354d..43da06d 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 @@ -289,6 +289,11 @@ x:Name="InvertButton" Command="{x:Bind ViewModel.ApplyInvertCommand}" Text="Invert" /> + + 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..b289540 --- /dev/null +++ b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..e5aaec5 --- /dev/null +++ b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml.cs @@ -0,0 +1,54 @@ +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; + + public RemoveBackgroundDialog(string imagePath) + { + InitializeComponent(); + _imagePath = imagePath; + } + + private async void ContentDialog_Loaded(object sender, RoutedEventArgs e) + { + try + { + BeforeImage.Source = new BitmapImage(new Uri(_imagePath)); + + bool available = await BackgroundRemoverHelper.IsAvailableAsync(); + if (!available) + { + 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; + } + + string resultPath = await BackgroundRemoverHelper.RemoveBackgroundAsync(_imagePath); + ResultImagePath = resultPath; + + AfterImage.Source = new BitmapImage(new Uri(resultPath)); + + ProcessingRing.IsActive = false; + IsPrimaryButtonEnabled = true; + } + catch (Exception ex) + { + StatusInfoBar.Title = "Error"; + StatusInfoBar.Message = $"Failed to remove background: {ex.Message}"; + StatusInfoBar.Severity = InfoBarSeverity.Error; + StatusInfoBar.IsOpen = true; + ProcessingRing.IsActive = false; + } + } +} From ee7df2ac2514b40f5a714cb97ea4e5307046e32b Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 21:01:55 -0600 Subject: [PATCH 6/9] Refine background removal ROI and mask logic, UI flow - Use the entire image as the region of interest for background removal instead of a central crop. - Invert the mask before applying it as alpha to correct background/foreground handling. - Store the result image path in RemoveBackgroundDialog only after user confirmation. - Add explicit PrimaryButtonClick handling for result commitment. - Refactor dialog loading logic for clearer error handling and UI state management. --- .../Helpers/BackgroundRemoverHelper.cs | 22 +++-------- .../Views/RemoveBackgroundDialog.xaml.cs | 39 ++++++++++++------- 2 files changed, 29 insertions(+), 32 deletions(-) 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 index 2350fcb..3889f5c 100644 --- a/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs +++ b/Simple Icon File Maker/Simple Icon File Maker/Helpers/BackgroundRemoverHelper.cs @@ -47,23 +47,11 @@ public static async Task RemoveBackgroundAsync(string imagePath) using ImageObjectExtractor extractor = await ImageObjectExtractor.CreateWithSoftwareBitmapAsync(sourceBitmap); - // Use a rectangle covering the central ~90% of the image so the model - // identifies the dominant object rather than a single small element. - int marginX = sourceBitmap.PixelWidth / 20; - int marginY = sourceBitmap.PixelHeight / 20; - - RectInt32 rect = new() - { - X = marginX, - Y = marginY, - Width = sourceBitmap.PixelWidth - 2 * marginX, - Height = sourceBitmap.PixelHeight - 2 * marginY - }; - + // Hint with the entire image rect as the region of interest ImageObjectExtractorHint hint = new( - includeRects: [rect], - includePoints: null, - excludePoints: null); + includeRects: [new RectInt32(0, 0, sourceBitmap.PixelWidth, sourceBitmap.PixelHeight)], + includePoints: [], + excludePoints: []); SoftwareBitmap mask = extractor.GetSoftwareBitmapObjectMask(hint); @@ -102,7 +90,7 @@ private static SoftwareBitmap ApplyMaskAsAlpha(SoftwareBitmap original, Software for (int i = 0; i < maskPixels.Length; i++) { int px = i * 4; - int m = maskPixels[i]; + 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); 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 index e5aaec5..be7f0d6 100644 --- 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 @@ -10,36 +10,42 @@ 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) { - try + BeforeImage.Source = new BitmapImage(new Uri(_imagePath)); + + bool isAvailable = await BackgroundRemoverHelper.IsAvailableAsync(); + if (!isAvailable) { - BeforeImage.Source = new BitmapImage(new Uri(_imagePath)); - - bool available = await BackgroundRemoverHelper.IsAvailableAsync(); - if (!available) - { - 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; - } + 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); - ResultImagePath = resultPath; + _pendingResultPath = resultPath; AfterImage.Source = new BitmapImage(new Uri(resultPath)); - ProcessingRing.IsActive = false; IsPrimaryButtonEnabled = true; } catch (Exception ex) @@ -48,6 +54,9 @@ private async void ContentDialog_Loaded(object sender, RoutedEventArgs e) StatusInfoBar.Message = $"Failed to remove background: {ex.Message}"; StatusInfoBar.Severity = InfoBarSeverity.Error; StatusInfoBar.IsOpen = true; + } + finally + { ProcessingRing.IsActive = false; } } From 5cd275c125e5e032e61aee84a007ad6313b2da31 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 19 Feb 2026 21:19:45 -0600 Subject: [PATCH 7/9] Bump app version to 1.15.0.0 in manifest and years to 2026 --- .../Simple Icon File Maker (Package)/Package.appxmanifest | 2 +- .../Simple Icon File Maker/Simple Icon File Maker.csproj | 2 +- .../Simple Icon File Maker/Views/AboutPage.xaml | 2 +- .../Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) 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 02dc6e6..d9637f5 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 @@ -10,7 +10,7 @@ + Version="1.15.0.0" /> 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 1df1c05..a4559fd 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 @@ -13,7 +13,7 @@ False False SimpleIconMaker.ico - Joseph Finney 2024 + Joseph Finney 2026 Image128.png enable enable 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..a6cd53b 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 @@ -25,7 +25,7 @@ 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 index b289540..ff7a949 100644 --- a/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml +++ b/Simple Icon File Maker/Simple Icon File Maker/Views/RemoveBackgroundDialog.xaml @@ -23,9 +23,7 @@ Text="Extract the main object from the image using on-device AI" TextWrapping="Wrap" /> - + From 3f1ea86c97b35a3765a1fdd890664f90cc001c40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:48:06 +0000 Subject: [PATCH 8/9] Initial plan From 10d100607ff5f5946d4b503b4e4370160e82255c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:52:46 +0000 Subject: [PATCH 9/9] Fix CI build: build main csproj directly instead of full solution to avoid wapproj issues Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a655285..d9a4e06 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: dotnet restore "Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj" - - name: Build solution - run: dotnet build "Simple Icon File Maker.sln" --configuration Release --no-restore + - name: Build + run: dotnet build "Simple Icon File Maker/Simple Icon File Maker/Simple Icon File Maker.csproj" --configuration Release --no-restore -p:Platform=x64