Super resolution with AI for image resizer (#42331)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
From WinAppSDK 1.8, microsoft announced a new feature AI Imaging. We can
use this ability to enhance our image resizer tools to support scale up
the image resolution by AI.

Doc:
https://learn.microsoft.com/en-us/windows/ai/apis/imaging#what-can-i-do-with-image-super-resolution

Target:
1. Add a new config to control use AI or not.
2. Support model download in image resizer.
3. Auto detect if user's computer support AI feature, if not, do not
show the AI related config.
4. Switch the control part if user enable/disable ai feature.

Demo:
Model not ready, user need to download the model:
<img width="694" height="625" alt="image"
src="https://github.com/user-attachments/assets/8079f047-71fa-4abf-b266-003f74cc5d3e"
/>

Model ready:
<img width="543" height="589" alt="image"
src="https://github.com/user-attachments/assets/952eafc6-0af6-4bea-88d0-0724532f4fac"
/>

User's computer doesn't support AI feature (x86 machine)
<img width="685" height="531" alt="image"
src="https://github.com/user-attachments/assets/522ba300-1505-46a2-a29b-3e8e71c49cdd"
/>


Note: **This feature only support for Arm Windows with the latest
Windows version.**


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: moooyo <lengyuchn@gmail.com>
Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
This commit is contained in:
moooyo
2025-12-15 09:42:49 +08:00
committed by GitHub
parent e13d6a78aa
commit 66e96bbe9d
22 changed files with 1705 additions and 84 deletions

View File

@@ -6,9 +6,9 @@
using System;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using ImageResizer.Models;
using ImageResizer.Properties;
using ImageResizer.Utilities;
@@ -20,8 +20,32 @@ namespace ImageResizer
{
public partial class App : Application, IDisposable
{
private const string LogSubFolder = "\\ImageResizer\\Logs";
/// <summary>
/// Gets cached AI availability state, checked at app startup.
/// Can be updated after model download completes or background initialization.
/// </summary>
public static AiAvailabilityState AiAvailabilityState { get; internal set; }
/// <summary>
/// Event fired when AI initialization completes in background.
/// Allows UI to refresh state when initialization finishes.
/// </summary>
public static event EventHandler<AiAvailabilityState> AiInitializationCompleted;
static App()
{
try
{
// Initialize logger early (mirroring PowerOCR pattern)
Logger.InitializeLogger(LogSubFolder);
}
catch
{
/* swallow logger init issues silently */
}
try
{
string appLanguage = LanguageHelper.LoadLanguage();
@@ -30,9 +54,9 @@ namespace ImageResizer
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException)
catch (CultureNotFoundException ex)
{
// error
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
Console.InputEncoding = Encoding.Unicode;
@@ -43,15 +67,59 @@ namespace ImageResizer
// Fix for .net 3.1.19 making Image Resizer not adapt to DPI changes.
NativeMethods.SetProcessDPIAware();
// Check for AI detection mode (called by Runner in background)
if (e?.Args?.Length > 0 && e.Args[0] == "--detect-ai")
{
RunAiDetectionMode();
return;
}
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredImageResizerEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{
/* TODO: Add logs to ImageResizer.
* Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
*/
Logger.LogWarning("GPO policy disables ImageResizer. Exiting.");
Environment.Exit(0); // Current.Exit won't work until there's a window opened.
return;
}
// AI Super Resolution is not supported on Windows 10 - skip cache check entirely
if (OSVersionHelper.IsWindows10())
{
AiAvailabilityState = AiAvailabilityState.NotSupported;
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
Logger.LogInfo("AI Super Resolution not supported on Windows 10");
}
else
{
// Load AI availability from cache (written by Runner's background detection)
var cachedState = Services.AiAvailabilityCacheService.LoadCache();
if (cachedState.HasValue)
{
AiAvailabilityState = cachedState.Value;
Logger.LogInfo($"AI state loaded from cache: {AiAvailabilityState}");
}
else
{
// No valid cache - default to NotSupported (Runner will detect and cache for next startup)
AiAvailabilityState = AiAvailabilityState.NotSupported;
Logger.LogInfo("No AI cache found, defaulting to NotSupported");
}
// If AI is potentially available, start background initialization (non-blocking)
if (AiAvailabilityState == AiAvailabilityState.Ready)
{
_ = InitializeAiServiceAsync(); // Fire and forget - don't block UI
}
else
{
// AI not available - set NoOp service immediately
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
}
}
var batch = ResizeBatch.FromCommandLine(Console.In, e?.Args);
// TODO: Add command-line parameters that can be used in lieu of the input page (issue #14)
@@ -62,9 +130,121 @@ namespace ImageResizer
WindowHelpers.BringToForeground(new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle);
}
/// <summary>
/// AI detection mode: perform detection, write to cache, and exit.
/// Called by Runner in background to avoid blocking ImageResizer UI startup.
/// </summary>
private void RunAiDetectionMode()
{
try
{
Logger.LogInfo("Running AI detection mode...");
// AI Super Resolution is not supported on Windows 10
if (OSVersionHelper.IsWindows10())
{
Logger.LogInfo("AI detection skipped: Windows 10 does not support AI Super Resolution");
Services.AiAvailabilityCacheService.SaveCache(AiAvailabilityState.NotSupported);
Environment.Exit(0);
return;
}
// Perform detection (reuse existing logic)
var state = CheckAiAvailability();
// Write result to cache file
Services.AiAvailabilityCacheService.SaveCache(state);
Logger.LogInfo($"AI detection complete: {state}");
}
catch (Exception ex)
{
Logger.LogError($"AI detection failed: {ex.Message}");
Services.AiAvailabilityCacheService.SaveCache(AiAvailabilityState.NotSupported);
}
// Exit silently without showing UI
Environment.Exit(0);
}
/// <summary>
/// Check AI Super Resolution availability on this system.
/// Performs architecture check and model availability check.
/// </summary>
private static AiAvailabilityState CheckAiAvailability()
{
try
{
// Check Windows AI service model ready state
// it's so slow, why?
var readyState = Services.WinAiSuperResolutionService.GetModelReadyState();
// Map AI service state to our availability state
switch (readyState)
{
case Microsoft.Windows.AI.AIFeatureReadyState.Ready:
return AiAvailabilityState.Ready;
case Microsoft.Windows.AI.AIFeatureReadyState.NotReady:
return AiAvailabilityState.ModelNotReady;
case Microsoft.Windows.AI.AIFeatureReadyState.DisabledByUser:
case Microsoft.Windows.AI.AIFeatureReadyState.NotSupportedOnCurrentSystem:
default:
return AiAvailabilityState.NotSupported;
}
}
catch (Exception)
{
return AiAvailabilityState.NotSupported;
}
}
/// <summary>
/// Initialize AI Super Resolution service asynchronously in background.
/// Runs without blocking UI startup - state change event notifies completion.
/// </summary>
private static async System.Threading.Tasks.Task InitializeAiServiceAsync()
{
AiAvailabilityState finalState;
try
{
// Create and initialize AI service using async factory
var aiService = await Services.WinAiSuperResolutionService.CreateAsync();
if (aiService != null)
{
ResizeBatch.SetAiSuperResolutionService(aiService);
Logger.LogInfo("AI Super Resolution service initialized successfully.");
finalState = AiAvailabilityState.Ready;
}
else
{
// Initialization failed - use default NoOp service
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
Logger.LogWarning("AI Super Resolution service initialization failed. Using default service.");
finalState = AiAvailabilityState.NotSupported;
}
}
catch (Exception ex)
{
// Log error and use default NoOp service
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
Logger.LogError($"Exception during AI service initialization: {ex.Message}");
finalState = AiAvailabilityState.NotSupported;
}
// Update cached state and notify listeners
AiAvailabilityState = finalState;
AiInitializationCompleted?.Invoke(null, finalState);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose AI Super Resolution service
ResizeBatch.DisposeAiSuperResolutionService();
GC.SuppressFinalize(this);
}
}