Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/winapp-CLI/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<PackageVersion Include="System.CommandLine" Version="2.0.0" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.0" />
<PackageVersion Include="Microsoft.Telemetry.Inbox.Managed" Version="10.0.25148.1001-220626-1600.rs-fun-deploy-dev5" />
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.0" />
</ItemGroup>
</Project>
</Project>
140 changes: 73 additions & 67 deletions src/winapp-CLI/WinApp.Cli/Services/ImageAssetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;
using SkiaSharp;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using WinApp.Cli.Helpers;

namespace WinApp.Cli.Services;
Expand Down Expand Up @@ -36,90 +38,94 @@ public async Task GenerateAssetsAsync(FileInfo sourceImagePath, DirectoryInfo ou
logger.LogInformation("{UISymbol} Generating MSIX image assets from: {SourceImage}", UiSymbols.Info, sourceImagePath.Name);

// Load the source image
using var sourceImage = SKBitmap.Decode(sourceImagePath.FullName);
if (sourceImage == null)
Bitmap sourceImage;
try
{
throw new InvalidOperationException($"Failed to decode image: {sourceImagePath.FullName}. Please ensure the file is a valid image format.");
sourceImage = new Bitmap(sourceImagePath.FullName);
}

logger.LogDebug("Source image size: {Width}x{Height}", sourceImage.Width, sourceImage.Height);

// Ensure output directory exists
if (!outputDirectory.Exists)
catch (Exception ex)
{
outputDirectory.Create();
throw new InvalidOperationException($"Failed to decode image: {sourceImagePath.FullName}. Please ensure the file is a valid image format.", ex);
}

// Generate each required asset
var successCount = 0;
foreach (var (fileName, width, height) in AssetSpecifications)
using (sourceImage)
{
try
logger.LogDebug("Source image size: {Width}x{Height}", sourceImage.Width, sourceImage.Height);

// Ensure output directory exists
if (!outputDirectory.Exists)
{
var outputPath = Path.Combine(outputDirectory.FullName, fileName);
await GenerateAssetAsync(sourceImage, outputPath, width, height, cancellationToken);
successCount++;
logger.LogDebug(" {UISymbol} Generated: {FileName} ({Width}x{Height})", UiSymbols.Check, fileName, width, height);
outputDirectory.Create();
}
catch (Exception ex)

// Generate each required asset
var successCount = 0;
foreach (var (fileName, width, height) in AssetSpecifications)
{
logger.LogWarning("{UISymbol} Failed to generate {FileName}: {ErrorMessage}", UiSymbols.Warning, fileName, ex.Message);
try
{
var outputPath = Path.Combine(outputDirectory.FullName, fileName);
await GenerateAssetAsync(sourceImage, outputPath, width, height, cancellationToken);
successCount++;
logger.LogDebug(" {UISymbol} Generated: {FileName} ({Width}x{Height})", UiSymbols.Check, fileName, width, height);
}
catch (Exception ex)
{
logger.LogWarning("{UISymbol} Failed to generate {FileName}: {ErrorMessage}", UiSymbols.Warning, fileName, ex.Message);
}
}
}

logger.LogInformation("{UISymbol} Successfully generated {Count} of {Total} image assets",
UiSymbols.Party, successCount, AssetSpecifications.Length);
logger.LogInformation("{UISymbol} Successfully generated {Count} of {Total} image assets",
UiSymbols.Party, successCount, AssetSpecifications.Length);
}
}

private static async Task GenerateAssetAsync(SKBitmap sourceImage, string outputPath, int targetWidth, int targetHeight, CancellationToken cancellationToken)
private static async Task GenerateAssetAsync(Bitmap sourceImage, string outputPath, int targetWidth, int targetHeight, CancellationToken cancellationToken)
{
// Calculate scaling to fit target dimensions while maintaining aspect ratio
var sourceAspect = (float)sourceImage.Width / sourceImage.Height;
var targetAspect = (float)targetWidth / targetHeight;

int scaledWidth, scaledHeight;
if (sourceAspect > targetAspect)
{
// Source is wider - fit to width
scaledWidth = targetWidth;
scaledHeight = (int)(targetWidth / sourceAspect);
}
else
await Task.Run(() =>
{
// Source is taller - fit to height
scaledHeight = targetHeight;
scaledWidth = (int)(targetHeight * sourceAspect);
}
// Calculate scaling to fit target dimensions while maintaining aspect ratio
var sourceAspect = (float)sourceImage.Width / sourceImage.Height;
var targetAspect = (float)targetWidth / targetHeight;

// Create the target bitmap with the required dimensions
using var targetBitmap = new SKBitmap(targetWidth, targetHeight);
using var canvas = new SKCanvas(targetBitmap);
int scaledWidth, scaledHeight;
if (sourceAspect > targetAspect)
{
// Source is wider - fit to width
scaledWidth = targetWidth;
scaledHeight = (int)(targetWidth / sourceAspect);
}
else
{
// Source is taller - fit to height
scaledHeight = targetHeight;
scaledWidth = (int)(targetHeight * sourceAspect);
}

// Fill with transparent background
canvas.Clear(SKColors.Transparent);
// Create the target bitmap with the required dimensions
using var targetBitmap = new Bitmap(targetWidth, targetHeight, PixelFormat.Format32bppArgb);
using var graphics = Graphics.FromImage(targetBitmap);

// Calculate position to center the scaled image
var destRect = new SKRect(
(targetWidth - scaledWidth) / 2f,
(targetHeight - scaledHeight) / 2f,
(targetWidth + scaledWidth) / 2f,
(targetHeight + scaledHeight) / 2f
);
// Set high-quality rendering options
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.CompositingMode = CompositingMode.SourceOver;

// Draw the scaled image
var paint = new SKPaint
{
IsAntialias = true
};
canvas.DrawBitmap(sourceImage, destRect, paint);

// Encode and save as PNG
using var image = SKImage.FromBitmap(targetBitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);

// Write to file asynchronously
await using var stream = File.Create(outputPath);
data.SaveTo(stream);
await stream.FlushAsync(cancellationToken);
// Fill with transparent background
graphics.Clear(Color.Transparent);

// Calculate position to center the scaled image
var x = (targetWidth - scaledWidth) / 2f;
var y = (targetHeight - scaledHeight) / 2f;
var destRect = new RectangleF(x, y, scaledWidth, scaledHeight);

// Draw the scaled image
graphics.DrawImage(sourceImage, destRect);

// Save as PNG
targetBitmap.Save(outputPath, ImageFormat.Png);
}, cancellationToken);
}
}
2 changes: 1 addition & 1 deletion src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.Diagnostics.EventLog" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>

<ItemGroup>
Expand Down