diff --git a/Xwt.XamMac/Xwt.Mac/ImageHandler.cs b/Xwt.XamMac/Xwt.Mac/ImageHandler.cs index 1b71ed01a..b8c15d387 100644 --- a/Xwt.XamMac/Xwt.Mac/ImageHandler.cs +++ b/Xwt.XamMac/Xwt.Mac/ImageHandler.cs @@ -40,10 +40,10 @@ namespace Xwt.Mac { public class ImageHandler: ImageBackendHandler { - static readonly IntPtr sel_alloc = new Selector ("alloc").Handle; - static readonly IntPtr sel_release = new Selector ("release").Handle; - static readonly IntPtr sel_initWithIconRef = new Selector ("initWithIconRef:").Handle; - static readonly IntPtr cls_NSImage = new Class (typeof (NSImage)).Handle; + static readonly IntPtr sel_alloc = Selector.GetHandle ("alloc"); + static readonly IntPtr sel_release = Selector.GetHandle ("release"); + static readonly IntPtr sel_initWithIconRef = Selector.GetHandle ("initWithIconRef:"); + static readonly IntPtr cls_NSImage = Class.GetHandle(typeof (NSImage)); static Dictionary stockIcons = new Dictionary (); diff --git a/Xwt/Xwt.Drawing/Image.cs b/Xwt/Xwt.Drawing/Image.cs index 64dd9aad8..e7b796765 100644 --- a/Xwt/Xwt.Drawing/Image.cs +++ b/Xwt/Xwt.Drawing/Image.cs @@ -31,6 +31,8 @@ using System.Reflection; using System.IO; using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Xwt.Drawing { @@ -42,6 +44,7 @@ public class Image: XwtObject, IDisposable internal StyleSet styles; internal static int[] SupportedScales = { 2 }; + internal static string[] SupportedScalesTags = SupportedScales.Select(scale => "@" + scale + "x").ToArray(); internal Image () { @@ -252,7 +255,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i tags = ImageTagSet.Empty; var firstDelimiter = fileName.IndexOfAny (tagDelimiters); - if (firstDelimiter <= 0 || fileName.Length <= baseName.Length + 1 || !fileName.Substring(0, firstDelimiter).Equals(baseName, StringComparison.Ordinal)) + if (firstDelimiter <= 0 || fileName.Length <= baseName.Length + 1 || string.Compare(fileName, 0, baseName, 0, firstDelimiter) != 0) return false; fileName = fileName.Substring (0, fileName.Length - ext.Length); @@ -268,7 +271,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } else i2 = fileName.Length; - tags = new ImageTagSet (fileName.Substring (i, i2 - i)); + tags = ImageTagSet.Parse (fileName.Substring (i, i2 - i)); return true; } else { @@ -288,7 +291,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } if (i2 + 2 < fileName.Length) - tags = new ImageTagSet (fileName.Substring (i2 + 2)); + tags = ImageTagSet.Parse (fileName.Substring (i2 + 2)); return true; } } @@ -307,18 +310,23 @@ public static Image CreateMultiSizeIcon (IEnumerable images) // If one of the images is themed, then the whole resulting image will be themed. // To create the new image, we group images with the same theme but different size, and we create a multi-size icon for those. // The resulting image is the combination of those multi-size icons. - var allThemes = allImages.OfType ().SelectMany (i => i.Images).Select (i => new ImageTagSet (i.Item2)).Distinct ().ToArray (); + var allThemes = allImages + .OfType () + .SelectMany (i => i.Images) + .Select(i => i.Item2) + .Distinct (TagSetEqualityComparer.Instance) + .ToArray (); List> newImages = new List> (); foreach (var ts in allThemes) { List multiSizeImages = new List (); foreach (var i in allImages) { if (i is ThemedImage) - multiSizeImages.Add (((ThemedImage)i).GetImage (ts.AsArray)); + multiSizeImages.Add (((ThemedImage)i).GetImage (ts)); else multiSizeImages.Add (i); } var img = CreateMultiSizeIcon (multiSizeImages); - newImages.Add (new Tuple (img, ts.AsArray)); + newImages.Add (new Tuple (img, ts)); } return new ThemedImage (newImages); } else { @@ -967,36 +975,123 @@ public void ReleaseReference (bool disposing) public NativeImageRef NextRef { get; set; } } - class ImageTagSet + sealed class ImageTagCache + { + /* Some stats from an app using Xwt: +1474 dark +1304 contrast +1296 contrast~dark + 846 sel + 846 dark~sel + 132 disabled + 120 dark~disabled + 116 contrast~disabled + 116 contrast~dark~disabled + 22 error + 22 dark~error + 22 contrast~error + 22 contrast~dark~error + 14 contrast~dark~sel + 12 contrast~sel + 6 disabled~dark + 6 dark~contrast + 2 sel~error + 2 pressed~dark + 2 pressed + 2 hover~dark + 2 hover + 2 dark~sel~error + 2 contrast~sel~error + 2 active~sel + 2 active~dark~sel + 2 active~dark + 2 active~contrast~dark + 2 active~contrast + 2 active + + Keep in sync with knownTagArrays. + These tag items amount for 97% of the image tags found in images. + */ + readonly string[] knownTags = new[] { + "dark", + "contrast", + "contrast~dark", + "sel", + "dark~sel", + "disabled", + "dark~disabled", + "contrast~disabled", + "contrast~dark~disabled", + }; + + readonly ImageTagSet[] knownTagArrays = new[] { + new ImageTagSet(new[] { "dark", }), + new ImageTagSet(new[] { "contrast", }), + new ImageTagSet(new[] { "contrast", "dark", }), + new ImageTagSet(new[] { "sel", }), + new ImageTagSet(new[] { "dark", "sel", }), + new ImageTagSet(new[] { "disabled", }), + new ImageTagSet(new[] { "dark", "disabled", }), + new ImageTagSet(new[] { "contrast", "disabled", }), + new ImageTagSet(new[] { "contrast", "dark", "disabled", }), + }; + + public ImageTagSet TryGetTagSet(string tags) + { + var index = Array.IndexOf(knownTags, tags); + return index >= 0 ? knownTagArrays[index] : null; + } + } + + // As much as I don't like the duplication, it's simpler than accessing a static instance every time. + class TagSetEqualityComparer : IEqualityComparer + { + public static TagSetEqualityComparer Instance { get; } = new TagSetEqualityComparer(); + + public bool Equals(string[] x, string[] y) => x.SequenceEqual(y); + + public int GetHashCode(string[] obj) + { + unchecked + { + int c = 0; + foreach (var s in obj) + c %= s.GetHashCode(); + return c; + } + } + } + + [DebuggerDisplay("{DebuggerDisplay,nq}")] + sealed class ImageTagSet { - string tags; string[] tagsArray; public static readonly ImageTagSet Empty = new ImageTagSet (new string[0]); + static readonly ImageTagCache imageTagCache = new ImageTagCache(); + static readonly char[] tagSeparators = { '~' }; - public ImageTagSet (string [] tagsArray) + public static ImageTagSet Parse(string tags) { - this.tagsArray = tagsArray; - Array.Sort (tagsArray); + return imageTagCache.TryGetTagSet(tags) ?? Create(tags); } - public bool IsEmpty { - get { - return tagsArray.Length == 0; - } + static ImageTagSet Create(string tags) + { + var tagArray = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries); + Array.Sort(tagArray); + + return new ImageTagSet(tagArray); } - public ImageTagSet (string tags) + public ImageTagSet (string [] tagsArray) { - tagsArray = tags.Split (new [] { '~' }, StringSplitOptions.RemoveEmptyEntries); - Array.Sort (AsArray); + this.tagsArray = tagsArray; } - public string AsString { + public bool IsEmpty { get { - if (tags == null) - tags = string.Join ("~", tagsArray); - return tags; + return tagsArray.Length == 0; } } @@ -1009,12 +1104,7 @@ public string [] AsArray { public override bool Equals (object obj) { var other = obj as ImageTagSet; - if (other == null || tagsArray.Length != other.tagsArray.Length) - return false; - for (int n = 0; n < tagsArray.Length; n++) - if (tagsArray [n] != other.tagsArray [n]) - return false; - return true; + return other != null && tagsArray.SequenceEqual(other.tagsArray); } public override int GetHashCode () @@ -1026,6 +1116,8 @@ public override int GetHashCode () return c; } } + + string DebuggerDisplay => string.Join("~", tagsArray); } abstract class ImageLoader @@ -1054,9 +1146,11 @@ public override object LoadImage (string fileName) return img; } + static ConditionalWeakTable resourceNamesCache = new ConditionalWeakTable(); public override IEnumerable GetAlternativeFiles (string fileName, string baseName, string ext) { - return assembly.GetManifestResourceNames ().Where (f => + var resourceNames = resourceNamesCache.GetValue(assembly, asm => asm.GetManifestResourceNames()); + return resourceNames.Where (f => f.StartsWith (baseName, StringComparison.Ordinal) && f.EndsWith (ext, StringComparison.Ordinal)); } @@ -1090,16 +1184,22 @@ public override object LoadImage (string fileName) public override IEnumerable GetAlternativeFiles (string fileName, string baseName, string ext) { - if (!Context.RegisteredStyles.Any ()) { - foreach (var s in Image.SupportedScales) { - var fn = baseName + "@" + s + "x" + ext; - if (File.Exists (fn)) - yield return fn; - } - } else { - var files = Directory.EnumerateFiles (Path.GetDirectoryName (fileName), Path.GetFileName (baseName) + "*" + ext); - foreach (var f in files) - yield return f; + if (!Context.RegisteredStyles.Any()) + { + return EnumerateFilesForRegisteredStyles(baseName, ext); + } + else + { + return Directory.EnumerateFiles(Path.GetDirectoryName(fileName), Path.GetFileName(baseName) + "*" + ext); + } + } + + IEnumerable EnumerateFilesForRegisteredStyles (string baseName, string ext) + { + foreach (var scaleTag in Image.SupportedScalesTags) { + var fn = baseName + scaleTag + ext; + if (File.Exists (fn)) + yield return fn; } }