| File: ResizetizeImages.cs | Web Access |
| Project: src\src\SingleProject\Resizetizer\src\Resizetizer.csproj (Microsoft.Maui.Resizetizer) |
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Microsoft.Maui.Resizetizer { public class ResizetizeImages : MauiAsyncTask, ILogger { [Required] public string PlatformType { get; set; } = "android"; [Required] public string IntermediateOutputPath { get; set; } public bool ThrowsErrorOnDuplicateOutput { get; set; } = true; public string DuplicateOutputErrorMessage { get; set; } public string InputsFile { get; set; } public ITaskItem[] Images { get; set; } [Output] public ITaskItem[] CopiedResources { get; set; } public string IsMacEnabled { get; set; } public ILogger Logger => this; public override System.Threading.Tasks.Task ExecuteAsync() { var inputImages = ResizeImageInfo.Parse(Images); var images = RemoveDuplicates(inputImages); var dpis = DpiPath.GetDpis(PlatformType); if (dpis == null || dpis.Length <= 0) return System.Threading.Tasks.Task.CompletedTask; var originalScaleDpi = DpiPath.GetOriginal(PlatformType); var resizedImages = new ConcurrentBag<ResizedImageInfo>(); this.ParallelForEach(images, img => { try { var opStopwatch = new Stopwatch(); opStopwatch.Start(); string op; if (img.IsAppIcon) { // App icons are special ProcessAppIcon(img, resizedImages); op = "App Icon"; } else { // By default we resize, but let's make sure if (img.Resize) { ProcessImageResize(img, dpis, resizedImages); op = "Resize"; } else { // Otherwise just copy the thing over to the 1.0 scale ProcessImageCopy(img, originalScaleDpi, resizedImages); op = "Copy"; } } opStopwatch.Stop(); LogDebugMessage($"{op} took {opStopwatch.ElapsedMilliseconds}ms"); } catch (Exception ex) { LogCodedError("MAUI0000", $"There was an exception processing the image '{img.Filename}': {ex}"); } }); if (PlatformType == "tizen") { var tizenResourceXmlGenerator = new TizenResourceXmlGenerator(IntermediateOutputPath, Logger); var r = tizenResourceXmlGenerator.Generate(); if (r is not null) resizedImages.Add(r); } var copiedResources = new List<TaskItem>(); foreach (var img in resizedImages) { var attr = new Dictionary<string, string>(StringComparer.Ordinal); string itemSpec = Path.GetFullPath(img.Filename); // Fix the item spec to be relative for mac if (bool.TryParse(IsMacEnabled, out bool isMac) && isMac) itemSpec = img.Filename; // Add DPI info to the itemspec so we can use it in the targets attr.Add("_ResizetizerDpiPath", img.Dpi.Path); attr.Add("_ResizetizerDpiScale", img.Dpi.Scale.ToString("0.0", CultureInfo.InvariantCulture)); copiedResources.Add(new TaskItem(itemSpec, attr)); // Make sure the file is not readonly Utils.SetWriteable(itemSpec); // force the date time so we never update an image if its not changed. File.SetLastWriteTimeUtc(itemSpec, DateTime.UtcNow); } CopiedResources = copiedResources.ToArray(); return System.Threading.Tasks.Task.CompletedTask; } IEnumerable<ResizeImageInfo> RemoveDuplicates(IEnumerable<ResizeImageInfo> inputImages) { var imagesPairs = new Dictionary<string, ResizeImageInfo>(); var builder = new StringBuilder(); builder.Append(DuplicateOutputErrorMessage); var hasDuplicates = false; foreach (var image in inputImages) { if (imagesPairs.ContainsKey(image.OutputName)) { if (hasDuplicates) builder.Append(", "); builder.Append($"{image.OutputName} ({image.ItemSpec})"); hasDuplicates = true; } imagesPairs[image.OutputName] = image; } if (hasDuplicates) { if (ThrowsErrorOnDuplicateOutput) Log.LogError(builder.ToString()); else Log.LogMessage(builder.ToString()); } return imagesPairs.Values; } void ProcessAppIcon(ResizeImageInfo img, ConcurrentBag<ResizedImageInfo> resizedImages) { var appIconName = img.OutputName; // Generate the actual bitmap app icons themselves var appIconDpis = DpiPath.GetAppIconDpis(PlatformType, appIconName); LogDebugMessage($"App Icon"); // Apple and Android have special additional files to generate for app icons if (PlatformType == "android") { LogDebugMessage($"Android Adaptive Icon Generator"); appIconName = appIconName.ToLowerInvariant(); var adaptiveIconGen = new AndroidAdaptiveIconGenerator(img, appIconName, IntermediateOutputPath, this); var iconsGenerated = adaptiveIconGen.Generate(); foreach (var iconGenerated in iconsGenerated) resizedImages.Add(iconGenerated); } else if (PlatformType == "ios") { LogDebugMessage($"iOS Icon Assets Generator"); var appleAssetGen = new AppleIconAssetsGenerator(img, appIconName, IntermediateOutputPath, appIconDpis, this); var assetsGenerated = appleAssetGen.Generate(); foreach (var assetGenerated in assetsGenerated) resizedImages.Add(assetGenerated); } else if (PlatformType == "uwp") { LogDebugMessage($"Windows Icon Generator"); var windowsIconGen = new WindowsIconGenerator(img, IntermediateOutputPath, this); resizedImages.Add(windowsIconGen.Generate()); } LogDebugMessage($"Generating App Icon Bitmaps for DPIs"); var appTool = new SkiaSharpAppIconTools(img, this); LogDebugMessage($"App Icon: Intermediate Path " + IntermediateOutputPath); foreach (var dpi in appIconDpis) { LogDebugMessage($"App Icon: " + dpi); var destination = Resizer.GetRasterFileDestination(img, dpi, IntermediateOutputPath) .Replace("{name}", appIconName); var (sourceExists, sourceModified) = Utils.FileExists(img.Filename); var (destinationExists, destinationModified) = Utils.FileExists(destination); LogDebugMessage($"App Icon Destination: " + destination); if (destinationModified > sourceModified) { Logger.Log($"Skipping `{img.Filename}` => `{destination}` file is up to date."); resizedImages.Add(new ResizedImageInfo() { Dpi = dpi, Filename = destination }); continue; } appTool.Resize(dpi, destination); var r = appTool.Resize(dpi, destination); resizedImages.Add(r); } } void ProcessImageResize(ResizeImageInfo img, DpiPath[] dpis, ConcurrentBag<ResizedImageInfo> resizedImages) { var resizer = new Resizer(img, IntermediateOutputPath, this); foreach (var dpi in dpis) { LogDebugMessage($"Resizing {img.Filename}"); var r = resizer.Resize(dpi, InputsFile); resizedImages.Add(r); LogDebugMessage($"Resized {img.Filename}"); } } void ProcessImageCopy(ResizeImageInfo img, DpiPath originalScaleDpi, ConcurrentBag<ResizedImageInfo> resizedImages) { var resizer = new Resizer(img, IntermediateOutputPath, this); LogDebugMessage($"Copying {img.Filename}"); var r = resizer.CopyFile(originalScaleDpi, InputsFile); resizedImages.Add(r); LogDebugMessage($"Copied {img.Filename}"); } void ILogger.Log(string message) { Log?.LogMessage(message); } } }