Skip to content

Commit b14d6c9

Browse files
committed
adding hash ability to image loader (reducing the load on memory)
1 parent c63e8d3 commit b14d6c9

File tree

5 files changed

+141
-20
lines changed

5 files changed

+141
-20
lines changed

Wox.Infrastructure/Image/ImageCache.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ public bool ContainsKey(string key)
3939
var contains = _data.ContainsKey(key);
4040
return contains;
4141
}
42+
43+
public int CacheSize()
44+
{
45+
return _data.Count;
46+
}
47+
48+
/// <summary>
49+
/// return the number of unique images in the cache (by reference not by checking images content)
50+
/// </summary>
51+
public int UniqueImagesInCache()
52+
{
53+
return _data.Values.Distinct().Count();
54+
}
4255
}
4356

4457
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.IO;
3+
using System.Security.Cryptography;
4+
using System.Windows.Media;
5+
using System.Windows.Media.Imaging;
6+
7+
namespace Wox.Infrastructure.Image
8+
{
9+
public interface IImageHashGenerator
10+
{
11+
string GetHashFromImage(ImageSource image);
12+
}
13+
public class ImageHashGenerator : IImageHashGenerator
14+
{
15+
public string GetHashFromImage(ImageSource imageSource)
16+
{
17+
if (!(imageSource is BitmapSource image))
18+
{
19+
return null;
20+
}
21+
22+
try
23+
{
24+
using (var outStream = new MemoryStream())
25+
{
26+
// PngBitmapEncoder enc2 = new PngBitmapEncoder();
27+
// enc2.Frames.Add(BitmapFrame.Create(tt));
28+
29+
var enc = new JpegBitmapEncoder();
30+
enc.Frames.Add(BitmapFrame.Create(image));
31+
enc.Save(outStream);
32+
var byteArray = outStream.GetBuffer();
33+
using (var sha1 = new SHA1CryptoServiceProvider())
34+
{
35+
var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray));
36+
return hash;
37+
}
38+
}
39+
}
40+
catch
41+
{
42+
return null;
43+
}
44+
45+
}
46+
}
47+
}

Wox.Infrastructure/Image/ImageLoader.cs

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Concurrent;
33
using System.IO;
44
using System.Linq;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using System.Windows.Media;
78
using System.Windows.Media.Imaging;
@@ -14,6 +15,8 @@ public static class ImageLoader
1415
{
1516
private static readonly ImageCache ImageCache = new ImageCache();
1617
private static BinaryStorage<ConcurrentDictionary<string, int>> _storage;
18+
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
19+
private static IImageHashGenerator _hashGenerator;
1720

1821

1922
private static readonly string[] ImageExtensions =
@@ -30,7 +33,8 @@ public static class ImageLoader
3033

3134
public static void Initialize()
3235
{
33-
_storage = new BinaryStorage<ConcurrentDictionary<string, int>> ("Image");
36+
_storage = new BinaryStorage<ConcurrentDictionary<string, int>>("Image");
37+
_hashGenerator = new ImageHashGenerator();
3438
ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary<string, int>());
3539

3640
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
@@ -43,16 +47,12 @@ public static void Initialize()
4347
{
4448
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
4549
{
46-
ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(i =>
50+
ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(x =>
4751
{
48-
var img = Load(i.Key);
49-
if (img != null)
50-
{
51-
ImageCache[i.Key] = img;
52-
}
52+
Load(x.Key);
5353
});
5454
});
55-
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>");
55+
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
5656
});
5757
}
5858

@@ -61,31 +61,54 @@ public static void Save()
6161
ImageCache.Cleanup();
6262
_storage.Save(ImageCache.Usage);
6363
}
64-
65-
public static ImageSource Load(string path, bool loadFullImage = false)
64+
65+
private class ImageResult
66+
{
67+
public ImageResult(ImageSource imageSource, ImageType imageType)
68+
{
69+
ImageSource = imageSource;
70+
ImageType = imageType;
71+
}
72+
73+
public ImageType ImageType { get; }
74+
public ImageSource ImageSource { get; }
75+
}
76+
77+
private enum ImageType
78+
{
79+
File,
80+
Folder,
81+
Data,
82+
ImageFile,
83+
Error,
84+
Cache
85+
}
86+
87+
private static ImageResult LoadInternal(string path, bool loadFullImage = false)
6688
{
6789
ImageSource image;
90+
ImageType type = ImageType.Error;
6891
try
6992
{
7093
if (string.IsNullOrEmpty(path))
7194
{
72-
return ImageCache[Constant.ErrorIcon];
95+
return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error);
7396
}
7497
if (ImageCache.ContainsKey(path))
7598
{
76-
return ImageCache[path];
99+
return new ImageResult(ImageCache[path], ImageType.Cache);
77100
}
78-
101+
79102
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
80103
{
81-
return new BitmapImage(new Uri(path));
104+
return new ImageResult(new BitmapImage(new Uri(path)), ImageType.Data);
82105
}
83106

84107
if (!Path.IsPathRooted(path))
85108
{
86109
path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path));
87110
}
88-
111+
89112
if (Directory.Exists(path))
90113
{
91114
/* Directories can also have thumbnails instead of shell icons.
@@ -94,14 +117,17 @@ public static ImageSource Load(string path, bool loadFullImage = false)
94117
* Wox responsibility.
95118
* - Solution: just load the icon
96119
*/
120+
type = ImageType.Folder;
97121
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
98122
Constant.ThumbnailSize, ThumbnailOptions.IconOnly);
123+
99124
}
100125
else if (File.Exists(path))
101126
{
102127
var extension = Path.GetExtension(path).ToLower();
103128
if (ImageExtensions.Contains(extension))
104129
{
130+
type = ImageType.ImageFile;
105131
if (loadFullImage)
106132
{
107133
image = LoadFullImage(path);
@@ -119,6 +145,7 @@ public static ImageSource Load(string path, bool loadFullImage = false)
119145
}
120146
else
121147
{
148+
type = ImageType.File;
122149
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
123150
Constant.ThumbnailSize, ThumbnailOptions.None);
124151
}
@@ -128,17 +155,51 @@ public static ImageSource Load(string path, bool loadFullImage = false)
128155
image = ImageCache[Constant.ErrorIcon];
129156
path = Constant.ErrorIcon;
130157
}
131-
ImageCache[path] = image;
132-
image.Freeze();
158+
159+
if (type != ImageType.Error)
160+
{
161+
image.Freeze();
162+
}
133163
}
134164
catch (System.Exception e)
135165
{
136166
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e);
137-
167+
type = ImageType.Error;
138168
image = ImageCache[Constant.ErrorIcon];
139169
ImageCache[path] = image;
140170
}
141-
return image;
171+
return new ImageResult(image, type);
172+
}
173+
174+
private static bool EnableImageHash = true;
175+
176+
public static ImageSource Load(string path, bool loadFullImage = false)
177+
{
178+
// return LoadInternal(path, loadFullImage).ImageSource;
179+
var imageResult = LoadInternal(path, loadFullImage);
180+
181+
var img = imageResult.ImageSource;
182+
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
183+
{ // we need to get image hash
184+
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
185+
if (hash != null)
186+
{
187+
if (GuidToKey.TryGetValue(hash, out string key))
188+
{ // image already exists
189+
img = ImageCache[key];
190+
}
191+
else
192+
{ // new guid
193+
GuidToKey[hash] = path;
194+
}
195+
}
196+
197+
// update cache
198+
ImageCache[path] = img;
199+
}
200+
201+
202+
return img;
142203
}
143204

144205
private static BitmapImage LoadFullImage(string path)

Wox.Infrastructure/Image/ThumbnailReader.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ public static BitmapSource GetThumbnail(string fileName, int width, int height,
110110

111111
try
112112
{
113-
114113
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
115114
}
116115
finally

Wox.Infrastructure/Wox.Infrastructure.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<Compile Include="Hotkey\InterceptKeys.cs" />
7272
<Compile Include="Hotkey\KeyEvent.cs" />
7373
<Compile Include="Image\ImageCache.cs" />
74+
<Compile Include="Image\ImageHashGenerator.cs" />
7475
<Compile Include="Image\ImageLoader.cs" />
7576
<Compile Include="Image\ThumbnailReader.cs" />
7677
<Compile Include="Logger\Log.cs" />

0 commit comments

Comments
 (0)