diff --git a/src/winapp-CLI/Directory.Packages.props b/src/winapp-CLI/Directory.Packages.props
index 9949ec17..e12da34b 100644
--- a/src/winapp-CLI/Directory.Packages.props
+++ b/src/winapp-CLI/Directory.Packages.props
@@ -8,6 +8,7 @@
+
diff --git a/src/winapp-CLI/WinApp.Cli.Tests/ManifestUpdateAssetsCommandTests.cs b/src/winapp-CLI/WinApp.Cli.Tests/ManifestUpdateAssetsCommandTests.cs
index 52a286e3..f6c3868b 100644
--- a/src/winapp-CLI/WinApp.Cli.Tests/ManifestUpdateAssetsCommandTests.cs
+++ b/src/winapp-CLI/WinApp.Cli.Tests/ManifestUpdateAssetsCommandTests.cs
@@ -255,7 +255,78 @@ public async Task ManifestUpdateAssetsCommandShouldInferManifestFromCurrentDirec
Assert.AreEqual(0, exitCode, "Update-assets command should complete successfully when manifest is inferred");
// Verify Assets directory was created
- var assetsDir = Path.Combine(_tempDirectory.FullName, "Assets");
- Assert.IsTrue(Directory.Exists(assetsDir), "Assets directory should be created");
- }
-}
+ var assetsDir = Path.Combine(_tempDirectory.FullName, "Assets");
+ Assert.IsTrue(Directory.Exists(assetsDir), "Assets directory should be created");
+ }
+
+ [TestMethod]
+ public async Task ManifestUpdateAssetsCommandShouldGenerateAssetsFromSvg()
+ {
+ // Arrange
+ var svgImagePath = Path.Combine(_tempDirectory.FullName, "testlogo.svg");
+ PngHelper.CreateTestSvgImage(svgImagePath);
+
+ var updateAssetsCommand = GetRequiredService();
+ var args = new[]
+ {
+ svgImagePath,
+ "--manifest", _testManifestPath
+ };
+
+ // Act
+ var parseResult = updateAssetsCommand.Parse(args);
+ var exitCode = await parseResult.InvokeAsync();
+
+ // Assert
+ Assert.AreEqual(0, exitCode, "Update-assets command should complete successfully with SVG source");
+
+ // Verify Assets directory was created
+ var assetsDir = Path.Combine(_tempDirectory.FullName, "Assets");
+ Assert.IsTrue(Directory.Exists(assetsDir), "Assets directory should be created");
+
+ // Verify assets referenced in manifest were generated
+ var expectedAssets = new[]
+ {
+ "Square44x44Logo.png",
+ "Square150x150Logo.png",
+ "Wide310x150Logo.png",
+ "StoreLogo.png"
+ };
+
+ foreach (var asset in expectedAssets)
+ {
+ var assetPath = Path.Combine(assetsDir, asset);
+ Assert.IsTrue(File.Exists(assetPath), $"Asset {asset} should be generated from SVG source");
+ }
+ }
+
+ [TestMethod]
+ public async Task ManifestUpdateAssetsCommandShouldGenerateCorrectSizesFromSvg()
+ {
+ // Arrange
+ var svgImagePath = Path.Combine(_tempDirectory.FullName, "testlogo.svg");
+ PngHelper.CreateTestSvgImage(svgImagePath);
+
+ var updateAssetsCommand = GetRequiredService();
+ var args = new[]
+ {
+ svgImagePath,
+ "--manifest", _testManifestPath
+ };
+
+ // Act
+ var parseResult = updateAssetsCommand.Parse(args);
+ var exitCode = await parseResult.InvokeAsync();
+
+ // Assert
+ Assert.AreEqual(0, exitCode, "Update-assets command should complete successfully with SVG source");
+
+ var assetsDir = Path.Combine(_tempDirectory.FullName, "Assets");
+
+ // Check that scale-200 assets exist (which should be 2x the base size)
+ Assert.IsTrue(File.Exists(Path.Combine(assetsDir, "Square44x44Logo.scale-200.png")),
+ "Square44x44Logo.scale-200.png should exist when generated from SVG");
+ Assert.IsTrue(File.Exists(Path.Combine(assetsDir, "Square150x150Logo.scale-200.png")),
+ "Square150x150Logo.scale-200.png should exist when generated from SVG");
+ }
+ }
diff --git a/src/winapp-CLI/WinApp.Cli.Tests/PngHelper.cs b/src/winapp-CLI/WinApp.Cli.Tests/PngHelper.cs
index e2edbd2b..8b2f8742 100644
--- a/src/winapp-CLI/WinApp.Cli.Tests/PngHelper.cs
+++ b/src/winapp-CLI/WinApp.Cli.Tests/PngHelper.cs
@@ -25,6 +25,16 @@ internal static void CreateTestImage(string path)
File.WriteAllBytes(path, pngData);
}
+ internal static void CreateTestSvgImage(string path)
+ {
+ var svgContent = """
+
+ """;
+ File.WriteAllText(path, svgContent);
+ }
+
///
/// Verifies that all pixels in the image are fully transparent (alpha = 0).
///
diff --git a/src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs b/src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs
index ca23ba26..3cd9e15f 100644
--- a/src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs
+++ b/src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation and Contributors. All rights reserved.
// Licensed under the MIT License.
+using SkiaSharp;
+using Svg.Skia;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
@@ -47,15 +49,7 @@ public async Task GenerateAssetsAsync(FileInfo sourceImagePath, DirectoryInfo ou
Bitmap sourceImage;
try
{
- if (sourceImagePath.Extension.Equals(".ico", StringComparison.OrdinalIgnoreCase))
- {
- using var icon = new Icon(sourceImagePath.FullName);
- sourceImage = icon.ToBitmap();
- }
- else
- {
- sourceImage = new Bitmap(sourceImagePath.FullName);
- }
+ sourceImage = LoadSourceImage(sourceImagePath);
}
catch (Exception ex)
{
@@ -123,15 +117,7 @@ public async Task GenerateAssetsFromManifestAsync(
Bitmap sourceImage;
try
{
- if (sourceImagePath.Extension.Equals(".ico", StringComparison.OrdinalIgnoreCase))
- {
- using var icon = new Icon(sourceImagePath.FullName);
- sourceImage = icon.ToBitmap();
- }
- else
- {
- sourceImage = new Bitmap(sourceImagePath.FullName);
- }
+ sourceImage = LoadSourceImage(sourceImagePath);
}
catch (Exception ex)
{
@@ -214,6 +200,62 @@ public async Task GenerateAssetsFromManifestAsync(
}
}
+ private static Bitmap LoadSourceImage(FileInfo sourceImagePath)
+ {
+ if (sourceImagePath.Extension.Equals(".ico", StringComparison.OrdinalIgnoreCase))
+ {
+ using var icon = new Icon(sourceImagePath.FullName);
+ return icon.ToBitmap();
+ }
+
+ if (sourceImagePath.Extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
+ {
+ var svg = new SKSvg();
+ using var stream = File.OpenRead(sourceImagePath.FullName);
+ svg.Load(stream);
+
+ var picture = svg.Picture ?? throw new InvalidOperationException($"Failed to render SVG image: {sourceImagePath.FullName}. The file may be corrupted or contain unsupported SVG features.");
+ var bounds = picture.CullRect;
+
+ int width = (int)Math.Ceiling(bounds.Width);
+ int height = (int)Math.Ceiling(bounds.Height);
+
+ // Ensure SVG renders at a reasonable minimum size for quality when scaling down
+ const float minRenderDimension = 1024f;
+ float scaleFactor = 1f;
+ if (width > 0 && height > 0 && (width < minRenderDimension || height < minRenderDimension))
+ {
+ scaleFactor = Math.Max(minRenderDimension / width, minRenderDimension / height);
+ width = (int)Math.Ceiling(bounds.Width * scaleFactor);
+ height = (int)Math.Ceiling(bounds.Height * scaleFactor);
+ }
+
+ // Render to SKBitmap
+ using var skBitmap = new SKBitmap(width, height);
+ using (var canvas = new SKCanvas(skBitmap))
+ {
+ canvas.Clear(SKColors.Transparent);
+ if (scaleFactor > 1f)
+ {
+ canvas.Scale(scaleFactor);
+ }
+ canvas.DrawPicture(picture);
+ canvas.Flush();
+ }
+
+ // Convert SKBitmap → System.Drawing.Bitmap
+ using var image = SKImage.FromBitmap(skBitmap);
+ using var data = image.Encode(SKEncodedImageFormat.Png, 100);
+ using var ms = new MemoryStream(data.ToArray());
+
+ var bitmap = new Bitmap(ms);
+
+ return bitmap;
+ }
+
+ return new Bitmap(sourceImagePath.FullName);
+ }
+
private static async Task GenerateAssetAsync(Bitmap sourceImage, string outputPath, int targetWidth, int targetHeight, CancellationToken cancellationToken)
{
await Task.Run(() =>
diff --git a/src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj b/src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
index bc98d6d3..8ecb1e98 100644
--- a/src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
+++ b/src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -26,7 +26,7 @@
full
true
true
-
+
false
false
@@ -54,6 +54,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+