File: AndroidAdaptiveIconGenerator.cs
Web Access
Project: src\src\SingleProject\Resizetizer\src\Resizetizer.csproj (Microsoft.Maui.Resizetizer)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
 
namespace Microsoft.Maui.Resizetizer
{
	internal class AndroidAdaptiveIconGenerator
	{
		public AndroidAdaptiveIconGenerator(ResizeImageInfo info, string appIconName, string intermediateOutputPath, ILogger logger)
		{
			Info = info;
			Logger = logger;
			IntermediateOutputPath = intermediateOutputPath;
			AppIconName = appIconName;
		}
 
		public ResizeImageInfo Info { get; }
 
		public string IntermediateOutputPath { get; }
 
		public ILogger Logger { get; private set; }
 
		public string AppIconName { get; }
 
		const string AdaptiveIconDrawableXml =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<adaptive-icon xmlns:android=""http://schemas.android.com/apk/res/android"">
	<background android:drawable=""@mipmap/{name}_background""/>
	<foreground android:drawable=""@mipmap/{name}_foreground""/>
	<monochrome android:drawable=""@mipmap/{name}_foreground"" />
</adaptive-icon>";
 
		public IEnumerable<ResizedImageInfo> Generate()
		{
			var sw = new Stopwatch();
			sw.Start();
 
			var results = new List<ResizedImageInfo>();
 
			var fullIntermediateOutputPath = new DirectoryInfo(IntermediateOutputPath);
 
			ProcessBackground(results, fullIntermediateOutputPath);
			ProcessForeground(results, fullIntermediateOutputPath);
			ProcessAdaptiveIcon(results, fullIntermediateOutputPath);
 
			sw.Stop();
			Logger?.Log($"Generating app icon took {sw.ElapsedMilliseconds}ms");
 
			return results;
		}
 
		void ProcessBackground(List<ResizedImageInfo> results, DirectoryInfo fullIntermediateOutputPath)
		{
			var backgroundFile = Info.Filename;
			var (backgroundExists, backgroundModified) = Utils.FileExists(backgroundFile);
			var backgroundDestFilename = AppIconName + "_background.png";
 
			if (backgroundExists)
				Logger.Log("Converting Background SVG to PNG: " + backgroundFile);
			else
				Logger.Log("Background was not found (will manufacture): " + backgroundFile);
 
			foreach (var dpi in DpiPath.Android.AppIconParts)
			{
				var dir = Path.Combine(fullIntermediateOutputPath.FullName, dpi.Path);
				var destination = Path.Combine(dir, backgroundDestFilename);
				var (destinationExists, destinationModified) = Utils.FileExists(destination);
				Directory.CreateDirectory(dir);
 
				if (destinationModified > backgroundModified)
				{
					Logger.Log($"Skipping `{backgroundFile}` => `{destination}` file is up to date.");
					results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination });
					continue;
				}
 
				Logger.Log($"App Icon Background Part: " + destination);
 
				if (backgroundExists)
				{
					// resize the background
					var tools = SkiaSharpTools.Create(Info.IsVector, Info.Filename, dpi.Size, Info.Color, null, Logger);
					tools.Resize(dpi, destination, dpiSizeIsAbsolute: true);
				}
				else
				{
					// manufacture
					var tools = SkiaSharpTools.CreateImaginary(Info.Color, Logger);
					tools.Resize(dpi, destination);
				}
 
				results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination });
			}
		}
 
		void ProcessForeground(List<ResizedImageInfo> results, DirectoryInfo fullIntermediateOutputPath)
		{
			var foregroundFile = Info.ForegroundFilename;
			var (foregroundExists, foregroundModified) = Utils.FileExists(foregroundFile);
			var foregroundDestFilename = AppIconName + "_foreground.png";
 
			if (foregroundExists)
				Logger.Log("Converting Foreground SVG to PNG: " + foregroundFile);
			else
				Logger.Log("Foreground was not found (will manufacture): " + foregroundFile);
 
			foreach (var dpi in DpiPath.Android.AppIconParts)
			{
				var dir = Path.Combine(fullIntermediateOutputPath.FullName, dpi.Path);
				var destination = Path.Combine(dir, foregroundDestFilename);
				var (destinationExists, destinationModified) = Utils.FileExists(destination);
				Directory.CreateDirectory(dir);
 
				if (destinationModified > foregroundModified)
				{
					Logger.Log($"Skipping `{foregroundFile}` => `{destination}` file is up to date.");
					results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination });
					continue;
				}
 
				Logger.Log($"App Icon Foreground Part: " + destination);
 
				if (foregroundExists)
				{
					// resize the forground
					var tools = SkiaSharpTools.Create(Info.ForegroundIsVector, Info.ForegroundFilename, dpi.Size, null, Info.TintColor, Logger);
					tools.Resize(dpi, destination, Info.ForegroundScale, dpiSizeIsAbsolute: true);
				}
				else
				{
					// manufacture
					var tools = SkiaSharpTools.CreateImaginary(null, Logger);
					tools.Resize(dpi, destination);
				}
 
				results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination });
			}
		}
 
		void ProcessAdaptiveIcon(List<ResizedImageInfo> results, DirectoryInfo fullIntermediateOutputPath)
		{
			var dir = Path.Combine(fullIntermediateOutputPath.FullName, "mipmap-anydpi-v26");
			var adaptiveIconDestination = Path.Combine(dir, AppIconName + ".xml");
			var adaptiveIconRoundDestination = Path.Combine(dir, AppIconName + "_round.xml");
			Directory.CreateDirectory(dir);
 
			if (File.Exists(adaptiveIconDestination) && File.Exists(adaptiveIconRoundDestination))
			{
				results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1), Filename = adaptiveIconDestination });
				results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1, "_round"), Filename = adaptiveIconRoundDestination });
				return;
			}
 
			var adaptiveIconXmlStr = AdaptiveIconDrawableXml
				.Replace("{name}", AppIconName);
 
			// Write out the adaptive icon xml drawables
			File.WriteAllText(adaptiveIconDestination, adaptiveIconXmlStr);
			File.WriteAllText(adaptiveIconRoundDestination, adaptiveIconXmlStr);
 
			results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1), Filename = adaptiveIconDestination });
			results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1, "_round"), Filename = adaptiveIconRoundDestination });
		}
	}
}