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

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Text;
using System.Text.Json.Serialization;
using ImageResizer.Properties;
namespace ImageResizer.Models
{
public class AiSize : ResizeSize
{
private static readonly CompositeFormat ScaleFormat = CompositeFormat.Parse(Resources.Input_AiScaleFormat);
private int _scale = 2;
/// <summary>
/// Gets the formatted scale display string (e.g., "2×").
/// </summary>
[JsonIgnore]
public string ScaleDisplay => string.Format(CultureInfo.CurrentCulture, ScaleFormat, _scale);
[JsonPropertyName("scale")]
public int Scale
{
get => _scale;
set => Set(ref _scale, value);
}
[JsonConstructor]
public AiSize(int scale)
{
Scale = scale;
}
public AiSize()
{
}
}
}

View File

@@ -15,17 +15,30 @@ using System.Threading;
using System.Threading.Tasks;
using ImageResizer.Properties;
using ImageResizer.Services;
namespace ImageResizer.Models
{
public class ResizeBatch
{
private readonly IFileSystem _fileSystem = new FileSystem();
private static IAISuperResolutionService _aiSuperResolutionService;
public string DestinationDirectory { get; set; }
public ICollection<string> Files { get; } = new List<string>();
public static void SetAiSuperResolutionService(IAISuperResolutionService service)
{
_aiSuperResolutionService = service;
}
public static void DisposeAiSuperResolutionService()
{
_aiSuperResolutionService?.Dispose();
_aiSuperResolutionService = null;
}
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
{
var batch = new ResizeBatch();
@@ -122,6 +135,9 @@ namespace ImageResizer.Models
}
protected virtual void Execute(string file, Settings settings)
=> new ResizeOperation(file, DestinationDirectory, settings).Execute();
{
var aiService = _aiSuperResolutionService ?? NoOpAiSuperResolutionService.Instance;
new ResizeOperation(file, DestinationDirectory, settings, aiService).Execute();
}
}
}

View File

@@ -10,12 +10,14 @@ using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ImageResizer.Extensions;
using ImageResizer.Properties;
using ImageResizer.Services;
using ImageResizer.Utilities;
using Microsoft.VisualBasic.FileIO;
@@ -30,6 +32,10 @@ namespace ImageResizer.Models
private readonly string _file;
private readonly string _destinationDirectory;
private readonly Settings _settings;
private readonly IAISuperResolutionService _aiSuperResolutionService;
// Cache CompositeFormat for AI error message formatting (CA1863)
private static readonly CompositeFormat _aiErrorFormat = CompositeFormat.Parse(Resources.Error_AiProcessingFailed);
// Filenames to avoid according to https://learn.microsoft.com/windows/win32/fileio/naming-a-file#file-and-directory-names
private static readonly string[] _avoidFilenames =
@@ -39,11 +45,12 @@ namespace ImageResizer.Models
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
};
public ResizeOperation(string file, string destinationDirectory, Settings settings)
public ResizeOperation(string file, string destinationDirectory, Settings settings, IAISuperResolutionService aiSuperResolutionService = null)
{
_file = file;
_destinationDirectory = destinationDirectory;
_settings = settings;
_aiSuperResolutionService = aiSuperResolutionService ?? NoOpAiSuperResolutionService.Instance;
}
public void Execute()
@@ -167,6 +174,11 @@ namespace ImageResizer.Models
private BitmapSource Transform(BitmapSource source)
{
if (_settings.SelectedSize is AiSize)
{
return TransformWithAi(source);
}
int originalWidth = source.PixelWidth;
int originalHeight = source.PixelHeight;
@@ -257,6 +269,31 @@ namespace ImageResizer.Models
return scaledBitmap;
}
private BitmapSource TransformWithAi(BitmapSource source)
{
try
{
var result = _aiSuperResolutionService.ApplySuperResolution(
source,
_settings.AiSize.Scale,
_file);
if (result == null)
{
throw new InvalidOperationException(Properties.Resources.Error_AiConversionFailed);
}
return result;
}
catch (Exception ex)
{
// Wrap the exception with a localized message
// This will be caught by ResizeBatch.Process() and displayed to the user
var errorMessage = string.Format(CultureInfo.CurrentCulture, _aiErrorFormat, ex.Message);
throw new InvalidOperationException(errorMessage, ex);
}
}
/// <summary>
/// Checks original metadata by writing an image containing the given metadata into a memory stream.
/// In case of errors, we try to rebuild the metadata object and check again.
@@ -363,19 +400,24 @@ namespace ImageResizer.Models
}
// Remove directory characters from the size's name.
string sizeNameSanitized = _settings.SelectedSize.Name;
sizeNameSanitized = sizeNameSanitized
// For AI Size, use the scale display (e.g., "2×") instead of the full name
string sizeName = _settings.SelectedSize is AiSize aiSize
? aiSize.ScaleDisplay
: _settings.SelectedSize.Name;
string sizeNameSanitized = sizeName
.Replace('\\', '_')
.Replace('/', '_');
// Using CurrentCulture since this is user facing
var selectedWidth = _settings.SelectedSize is AiSize ? encoder.Frames[0].PixelWidth : _settings.SelectedSize.Width;
var selectedHeight = _settings.SelectedSize is AiSize ? encoder.Frames[0].PixelHeight : _settings.SelectedSize.Height;
var fileName = string.Format(
CultureInfo.CurrentCulture,
_settings.FileNameFormat,
originalFileName,
sizeNameSanitized,
_settings.SelectedSize.Width,
_settings.SelectedSize.Height,
selectedWidth,
selectedHeight,
encoder.Frames[0].PixelWidth,
encoder.Frames[0].PixelHeight);