Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/winapp-CLI/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.2" />
<PackageVersion Include="Microsoft.Telemetry.Inbox.Managed" Version="10.0.25148.1001-220626-1600.rs-fun-deploy-dev5" />
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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<ManifestUpdateAssetsCommand>();
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<ManifestUpdateAssetsCommand>();
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");
}
}
10 changes: 10 additions & 0 deletions src/winapp-CLI/WinApp.Cli.Tests/PngHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ internal static void CreateTestImage(string path)
File.WriteAllBytes(path, pngData);
}

internal static void CreateTestSvgImage(string path)
{
var svgContent = """
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<rect width="100" height="100" fill="blue"/>
</svg>
""";
File.WriteAllText(path, svgContent);
}

/// <summary>
/// Verifies that all pixels in the image are fully transparent (alpha = 0).
/// </summary>
Expand Down
78 changes: 60 additions & 18 deletions src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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(() =>
Expand Down
5 changes: 3 additions & 2 deletions src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down Expand Up @@ -26,7 +26,7 @@
<TrimMode>full</TrimMode>
<CsWin32RunAsBuildTask>true</CsWin32RunAsBuildTask>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>

<!-- Faster startup for single-file -->
<IncludeNativeLibrariesForSelfExtract>false</IncludeNativeLibrariesForSelfExtract>
<DebuggerSupport>false</DebuggerSupport>
Expand Down Expand Up @@ -54,6 +54,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Svg.Skia" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.Diagnostics.EventLog" />
<PackageReference Include="System.Drawing.Common" />
Expand Down