Skip to content

Commit

Permalink
Remove unused tags string and improvements to caching
Browse files Browse the repository at this point in the history
We cache the ImageTagSet directly now. That should allow us to create fewer of these instances overall.
For the app I'm testing on, we cover 97% of the image tags combinations
In practice, cache hits will be better overall.

The other improvement is to not have to sort/create imagetagsets every time, instead opting to use more specialized APIs.
  • Loading branch information
Therzok committed Feb 21, 2023
1 parent 30ee113 commit 1557d48
Showing 1 changed file with 58 additions and 38 deletions.
96 changes: 58 additions & 38 deletions Xwt/Xwt.Drawing/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

namespace Xwt.Drawing
{
Expand Down Expand Up @@ -268,7 +269,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 {
Expand All @@ -288,7 +289,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;
}
}
Expand All @@ -307,18 +308,23 @@ public static Image CreateMultiSizeIcon (IEnumerable<Image> 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<ThemedImage> ().SelectMany (i => i.Images).Select (i => new ImageTagSet (i.Item2)).Distinct ().ToArray ();
var allThemes = allImages
.OfType<ThemedImage> ()
.SelectMany (i => i.Images)
.Select(i => i.Item2)
.Distinct (TagSetEqualityComparer.Instance)
.ToArray ();
List<Tuple<Image, string []>> newImages = new List<Tuple<Image, string []>> ();
foreach (var ts in allThemes) {
List<Image> multiSizeImages = new List<Image> ();
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<Image, string []> (img, ts.AsArray));
newImages.Add (new Tuple<Image, string[]> (img, ts));
}
return new ThemedImage (newImages);
} else {
Expand Down Expand Up @@ -1000,6 +1006,9 @@ 2 hover
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",
Expand All @@ -1013,65 +1022,74 @@ 2 active
"~contrast~dark~disabled",
};

readonly string[][] knownTagArrays = new[] {
new[] { "dark", },
new[] { "contrast", },
new[] { "contrast", "dark", },
new[] { "sel", },
new[] { "dark", "sel", },
new[] { "disabled", },
new[] { "dark", "disabled", },
new[] { "contrast", "disabled", },
new[] { "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", }),
};

static readonly char[] tagSeparators = { '~' };
public bool GetTagArray(string tags, out string[] tagArray)
public ImageTagSet TryGetTagSet(string tags)
{
var index = Array.IndexOf(knownTags, tags);
tagArray = index >= 0 ? knownTagArrays[index] : SplitTags(tags);
return index >= 0;
return index >= 0 ? knownTagArrays[index] : null;
}
}

static string[] SplitTags(string tags)
{
var array = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries);
Array.Sort(array);
// As much as I don't like the duplication, it's simpler than accessing a static instance every time.
class TagSetEqualityComparer : IEqualityComparer<string[]>
{
public static TagSetEqualityComparer Instance { get; } = new TagSetEqualityComparer();

public bool Equals(string[] x, string[] y) => x.SequenceEqual(y);

return array;
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;
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)
{
imageTagCache.GetTagArray(tags, out tagsArray);
this.tagsArray = tagsArray;
}

public string AsString {
public bool IsEmpty {
get {
if (tags == null)
tags = string.Join ("~", tagsArray);
return tags;
return tagsArray.Length == 0;
}
}

Expand All @@ -1096,6 +1114,8 @@ public override int GetHashCode ()
return c;
}
}

string DebuggerDisplay => string.Join("~", tagsArray);
}

abstract class ImageLoader
Expand Down

0 comments on commit 1557d48

Please sign in to comment.