public override void Execute() { JsConfig.IncludeNullValues = false; JsConfig.EmitCamelCaseNames = true; foreach (var platform in Configuration.Platforms) { var fileName = Path.Combine(WorkingDirectory, platform.IconFileName); var fileInfo = new FileInfo(fileName); if (!File.Exists(fileInfo.FullName)) { Log.Error($"Icon not found ({fileInfo.FullName})"); continue; } var svg = SkiaHelper.Load(fileInfo.FullName); var sourceActualWidth = svg.Picture.CullRect.Width; var sourceActualHeight = svg.Picture.CullRect.Height; var assets = new List <ExportAsset>(); var platformPath = new DirectoryInfo(Path.Combine(WorkingDirectory, platform.ProjectPath)); if (!platformPath.Exists) { Log.Error($"Platform path for {platform.Type} not found ({platformPath.FullName})"); continue; } switch (platform.Type) { case Platforms.iOS: { var targetPath = Path.Combine(platformPath.FullName, "Assets.xcassets", "AppIcon.appiconset"); var sizes = new[] { 20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024 }; assets.AddRange(sizes.Select(size => new ExportAsset(fileInfo.FullName, targetPath, $"Icon-App-{size}x{size}.png", sourceActualWidth, sourceActualHeight, size, size))); break; } case Platforms.AppleWatch: { var targetPath = Path.Combine(platformPath.FullName, "Assets.xcassets", "AppIcon.appiconset"); var sizes = new[] { 48, 55, 58, 80, 87, 88, 172, 196 }; assets.AddRange(sizes.Select(size => new ExportAsset(fileInfo.FullName, targetPath, $"Icon-App-{size}x{size}.png", sourceActualWidth, sourceActualHeight, size, size))); break; } case Platforms.MacOs: { var targetPath = Path.Combine(platformPath.FullName, "Assets.xcassets", "AppIcon.appiconset"); var sizes = new[] { 16, 32, 64, 128, 256, 512, 1024 }; assets.AddRange(sizes.Select(size => new ExportAsset(fileInfo.FullName, targetPath, $"Icon-App-{size}x{size}.png", sourceActualWidth, sourceActualHeight, size, size))); break; } case Platforms.Android: { var folderPrefix = "mipmap"; if (platform.AndroidOptions != null && !string.IsNullOrEmpty(platform.AndroidOptions.AssetFolderPrefix.ToString())) { folderPrefix = platform.AndroidOptions.AssetFolderPrefix.ToString(); } assets.Add(new ExportAsset(fileInfo.FullName, Path.Combine(platformPath.FullName, "Resources", $"{folderPrefix}-mdpi"), "Icon.png", sourceActualWidth, sourceActualHeight, 48, 48)); assets.Add(new ExportAsset(fileInfo.FullName, Path.Combine(platformPath.FullName, "Resources", $"{folderPrefix}-hdpi"), "Icon.png", sourceActualWidth, sourceActualHeight, 72, 72)); assets.Add(new ExportAsset(fileInfo.FullName, Path.Combine(platformPath.FullName, "Resources", $"{folderPrefix}-xhdpi"), "Icon.png", sourceActualWidth, sourceActualHeight, 96, 96)); assets.Add(new ExportAsset(fileInfo.FullName, Path.Combine(platformPath.FullName, "Resources", $"{folderPrefix}-xxhdpi"), "Icon.png", sourceActualWidth, sourceActualHeight, 144, 144)); assets.Add(new ExportAsset(fileInfo.FullName, Path.Combine(platformPath.FullName, "Resources", $"{folderPrefix}-xxxhdpi"), "Icon.png", sourceActualWidth, sourceActualHeight, 192, 192)); break; } case Platforms.UWP: { // https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/app-assets#asset-size-tables var targetPath = Path.Combine(platformPath.FullName, "Assets"); var sizes = new[] { 71, 150, 310, 44 }; var scales = new[] { 100, 125, 150, 200, 400 }; foreach (var size in sizes) { assets.AddRange(scales.Select(scale => new ExportAsset(fileInfo.FullName, targetPath, $"Square{size}x{size}Logo.scale-{scale}.png", sourceActualWidth, sourceActualHeight, size, size))); } break; } default: throw new ArgumentOutOfRangeException(); } foreach (var asset in assets) { var bmp = new SKBitmap((int)asset.ScaledWidth, (int)asset.ScaledHeight); var canvas = new SKCanvas(bmp); var matrix = SKMatrix.MakeScale((float)asset.Ratio, (float)asset.Ratio); canvas.Clear(SKColors.Transparent); canvas.DrawPicture(svg.Picture, ref matrix); canvas.Save(); // Export the canvas var img = SKImage.FromBitmap(bmp); var data = img.Encode(SKEncodedImageFormat.Png, 100); asset.FileName.EnsureFolderExists(); using (var fs = File.Open(asset.FileName, FileMode.Create)) { Log.Information($"Writing {asset.FileName}"); data.SaveTo(fs); } var platformProjectFolder = Path.Combine(WorkingDirectory, platform.ProjectPath); var destinationFolder = "Resources"; if (platform.Type == Platforms.UWP) { destinationFolder = "Assets"; } var destinationFile = Path.Combine(WorkingDirectory, platform.ProjectPath, destinationFolder, asset.FileName); switch (platform.Type) { case Platforms.iOS: case Platforms.AppleWatch: case Platforms.MacOs: FluentMSBuild.Use(destinationFile).WithBuildAction(BuildAction.ImageAsset).AddToProject(); break; case Platforms.Android: FluentMSBuild.Use(destinationFile).WithBuildAction(BuildAction.AndroidResource).AddToProject(); break; case Platforms.UWP: FluentMSBuild.Use(destinationFile).WithBuildAction(BuildAction.Content).AddToProject(); break; } } if (platform.Type == Platforms.iOS || platform.Type == Platforms.AppleWatch || platform.Type == Platforms.MacOs) { var contentsFile = Path.Combine(platformPath.FullName, "Assets.xcassets", "AppIcon.appiconset", "Contents.json"); Log.Information($"Writing {contentsFile}"); var contents = File.ReadAllText(contentsFile).FromJson <XcodeContents>(); foreach (var contentsImage in contents.Images) { var process = false; switch (platform.Type) { case Platforms.iOS when contentsImage.Idiom == "iphone" || contentsImage.Idiom == "ipad" || contentsImage.Idiom == "ios-marketing": case Platforms.AppleWatch when contentsImage.Idiom == "watch": case Platforms.MacOs when contentsImage.Idiom == "mac": process = true; break; } if (process) { // calculate needed size var size = double.Parse(contentsImage.Size.Split('x')[0], NumberStyles.Float, new CultureInfo("en-US")); var scale = double.Parse(contentsImage.Scale.Substring(0, 1)); var foundSize = assets.FirstOrDefault(a => a.ScaledWidth == size * scale); if (foundSize != null) { contentsImage.Filename = new FileInfo(foundSize.FileName).Name; } } } File.WriteAllText(contentsFile, contents.ToJson().IndentJson()); } } }