using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using SkiaSharp;
using Svg.Skia;
namespace Microsoft.Maui.Platform.Linux.Services;
///
/// Generates application icons from MAUI icon metadata.
/// Uses SVG overlay support via Svg.Skia package.
///
public static class MauiIconGenerator
{
private const int DefaultIconSize = 256;
public static string? GenerateIcon(string metaFilePath)
{
if (!File.Exists(metaFilePath))
{
DiagnosticLog.Error("MauiIconGenerator", "Metadata file not found: " + metaFilePath);
return null;
}
try
{
string path = Path.GetDirectoryName(metaFilePath) ?? "";
var metadata = ParseMetadata(File.ReadAllText(metaFilePath));
// bg and fg paths (bg not currently used, but available for future)
Path.Combine(path, "appicon_bg.svg");
string fgPath = Path.Combine(path, "appicon_fg.svg");
string outputPath = Path.Combine(path, "appicon.png");
// Parse size from metadata or use default
int size = metadata.TryGetValue("Size", out var sizeStr) && int.TryParse(sizeStr, out var sizeVal)
? sizeVal
: DefaultIconSize;
// Parse color from metadata or use default purple
SKColor color = metadata.TryGetValue("Color", out var colorStr)
? ParseColor(colorStr)
: SKColors.Purple;
// Parse scale from metadata or use default 0.65
float scale = metadata.TryGetValue("Scale", out var scaleStr) && float.TryParse(scaleStr, out var scaleVal)
? scaleVal
: 0.65f;
DiagnosticLog.Debug("MauiIconGenerator", $"Generating {size}x{size} icon");
DiagnosticLog.Debug("MauiIconGenerator", $" Color: {color}");
DiagnosticLog.Debug("MauiIconGenerator", $" Scale: {scale}");
using var surface = SKSurface.Create(new SKImageInfo(size, size, SKColorType.Bgra8888, SKAlphaType.Premul));
var canvas = surface.Canvas;
// Fill background with color
canvas.Clear(color);
// Load and draw SVG foreground if it exists
if (File.Exists(fgPath))
{
using var svg = new SKSvg();
if (svg.Load(fgPath) != null && svg.Picture != null)
{
var cullRect = svg.Picture.CullRect;
float svgScale = size * scale / Math.Max(cullRect.Width, cullRect.Height);
float offsetX = (size - cullRect.Width * svgScale) / 2f;
float offsetY = (size - cullRect.Height * svgScale) / 2f;
canvas.Save();
canvas.Translate(offsetX, offsetY);
canvas.Scale(svgScale);
canvas.DrawPicture(svg.Picture);
canvas.Restore();
}
}
using var image = surface.Snapshot();
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var fileStream = File.OpenWrite(outputPath);
data.SaveTo(fileStream);
DiagnosticLog.Debug("MauiIconGenerator", "Generated: " + outputPath);
return outputPath;
}
catch (Exception ex)
{
DiagnosticLog.Error("MauiIconGenerator", "Error: " + ex.Message);
return null;
}
}
private static Dictionary ParseMetadata(string content)
{
var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var parts = line.Split('=', 2);
if (parts.Length == 2)
{
result[parts[0].Trim()] = parts[1].Trim();
}
}
return result;
}
private static SKColor ParseColor(string colorStr)
{
if (string.IsNullOrEmpty(colorStr))
{
return SKColors.Purple;
}
colorStr = colorStr.Trim();
if (colorStr.StartsWith("#"))
{
string hex = colorStr.Substring(1);
// Expand 3-digit hex to 6-digit
if (hex.Length == 3)
{
hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}";
}
if (hex.Length == 6 && uint.TryParse(hex, NumberStyles.HexNumber, null, out var rgb))
{
return new SKColor(
(byte)((rgb >> 16) & 0xFF),
(byte)((rgb >> 8) & 0xFF),
(byte)(rgb & 0xFF));
}
if (hex.Length == 8 && uint.TryParse(hex, NumberStyles.HexNumber, null, out var argb))
{
return new SKColor(
(byte)((argb >> 16) & 0xFF),
(byte)((argb >> 8) & 0xFF),
(byte)(argb & 0xFF),
(byte)((argb >> 24) & 0xFF));
}
}
return colorStr.ToLowerInvariant() switch
{
"red" => SKColors.Red,
"green" => SKColors.Green,
"blue" => SKColors.Blue,
"purple" => SKColors.Purple,
"orange" => SKColors.Orange,
"white" => SKColors.White,
"black" => SKColors.Black,
_ => SKColors.Purple,
};
}
}