From b14d6c9216209db195530e3b0aee67a4657dac13 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:16:17 +0200 Subject: [PATCH 1/6] adding hash ability to image loader (reducing the load on memory) --- Wox.Infrastructure/Image/ImageCache.cs | 13 +++ .../Image/ImageHashGenerator.cs | 47 +++++++++ Wox.Infrastructure/Image/ImageLoader.cs | 99 +++++++++++++++---- Wox.Infrastructure/Image/ThumbnailReader.cs | 1 - Wox.Infrastructure/Wox.Infrastructure.csproj | 1 + 5 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 Wox.Infrastructure/Image/ImageHashGenerator.cs diff --git a/Wox.Infrastructure/Image/ImageCache.cs b/Wox.Infrastructure/Image/ImageCache.cs index 599b69066d..5e74a2a380 100644 --- a/Wox.Infrastructure/Image/ImageCache.cs +++ b/Wox.Infrastructure/Image/ImageCache.cs @@ -39,6 +39,19 @@ namespace Wox.Infrastructure.Image var contains = _data.ContainsKey(key); return contains; } + + public int CacheSize() + { + return _data.Count; + } + + /// + /// return the number of unique images in the cache (by reference not by checking images content) + /// + public int UniqueImagesInCache() + { + return _data.Values.Distinct().Count(); + } } } diff --git a/Wox.Infrastructure/Image/ImageHashGenerator.cs b/Wox.Infrastructure/Image/ImageHashGenerator.cs new file mode 100644 index 0000000000..96361d8155 --- /dev/null +++ b/Wox.Infrastructure/Image/ImageHashGenerator.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Wox.Infrastructure.Image +{ + public interface IImageHashGenerator + { + string GetHashFromImage(ImageSource image); + } + public class ImageHashGenerator : IImageHashGenerator + { + public string GetHashFromImage(ImageSource imageSource) + { + if (!(imageSource is BitmapSource image)) + { + return null; + } + + try + { + using (var outStream = new MemoryStream()) + { + // PngBitmapEncoder enc2 = new PngBitmapEncoder(); + // enc2.Frames.Add(BitmapFrame.Create(tt)); + + var enc = new JpegBitmapEncoder(); + enc.Frames.Add(BitmapFrame.Create(image)); + enc.Save(outStream); + var byteArray = outStream.GetBuffer(); + using (var sha1 = new SHA1CryptoServiceProvider()) + { + var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray)); + return hash; + } + } + } + catch + { + return null; + } + + } + } +} \ No newline at end of file diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 3498e4f3b2..8479ae94a6 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -14,6 +15,8 @@ namespace Wox.Infrastructure.Image { private static readonly ImageCache ImageCache = new ImageCache(); private static BinaryStorage> _storage; + private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); + private static IImageHashGenerator _hashGenerator; private static readonly string[] ImageExtensions = @@ -30,7 +33,8 @@ namespace Wox.Infrastructure.Image public static void Initialize() { - _storage = new BinaryStorage> ("Image"); + _storage = new BinaryStorage>("Image"); + _hashGenerator = new ImageHashGenerator(); ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary()); foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon }) @@ -43,16 +47,12 @@ namespace Wox.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(i => + ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(x => { - var img = Load(i.Key); - if (img != null) - { - ImageCache[i.Key] = img; - } + Load(x.Key); }); }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>"); + Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); } @@ -61,31 +61,54 @@ namespace Wox.Infrastructure.Image ImageCache.Cleanup(); _storage.Save(ImageCache.Usage); } - - public static ImageSource Load(string path, bool loadFullImage = false) + + private class ImageResult + { + public ImageResult(ImageSource imageSource, ImageType imageType) + { + ImageSource = imageSource; + ImageType = imageType; + } + + public ImageType ImageType { get; } + public ImageSource ImageSource { get; } + } + + private enum ImageType + { + File, + Folder, + Data, + ImageFile, + Error, + Cache + } + + private static ImageResult LoadInternal(string path, bool loadFullImage = false) { ImageSource image; + ImageType type = ImageType.Error; try { if (string.IsNullOrEmpty(path)) { - return ImageCache[Constant.ErrorIcon]; + return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error); } if (ImageCache.ContainsKey(path)) { - return ImageCache[path]; + return new ImageResult(ImageCache[path], ImageType.Cache); } - + if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { - return new BitmapImage(new Uri(path)); + return new ImageResult(new BitmapImage(new Uri(path)), ImageType.Data); } if (!Path.IsPathRooted(path)) { path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path)); } - + if (Directory.Exists(path)) { /* Directories can also have thumbnails instead of shell icons. @@ -94,14 +117,17 @@ namespace Wox.Infrastructure.Image * Wox responsibility. * - Solution: just load the icon */ + type = ImageType.Folder; image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.IconOnly); + } else if (File.Exists(path)) { var extension = Path.GetExtension(path).ToLower(); if (ImageExtensions.Contains(extension)) { + type = ImageType.ImageFile; if (loadFullImage) { image = LoadFullImage(path); @@ -119,6 +145,7 @@ namespace Wox.Infrastructure.Image } else { + type = ImageType.File; image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.None); } @@ -128,17 +155,51 @@ namespace Wox.Infrastructure.Image image = ImageCache[Constant.ErrorIcon]; path = Constant.ErrorIcon; } - ImageCache[path] = image; - image.Freeze(); + + if (type != ImageType.Error) + { + image.Freeze(); + } } catch (System.Exception e) { Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); - + type = ImageType.Error; image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; } - return image; + return new ImageResult(image, type); + } + + private static bool EnableImageHash = true; + + public static ImageSource Load(string path, bool loadFullImage = false) + { + // return LoadInternal(path, loadFullImage).ImageSource; + var imageResult = LoadInternal(path, loadFullImage); + + var img = imageResult.ImageSource; + if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) + { // we need to get image hash + string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; + if (hash != null) + { + if (GuidToKey.TryGetValue(hash, out string key)) + { // image already exists + img = ImageCache[key]; + } + else + { // new guid + GuidToKey[hash] = path; + } + } + + // update cache + ImageCache[path] = img; + } + + + return img; } private static BitmapImage LoadFullImage(string path) diff --git a/Wox.Infrastructure/Image/ThumbnailReader.cs b/Wox.Infrastructure/Image/ThumbnailReader.cs index e0ea9bba35..bd65fc7000 100644 --- a/Wox.Infrastructure/Image/ThumbnailReader.cs +++ b/Wox.Infrastructure/Image/ThumbnailReader.cs @@ -110,7 +110,6 @@ namespace Wox.Infrastructure.Image try { - return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } finally diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index af76894ed4..bd14c56034 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -71,6 +71,7 @@ + From 72e1a19ea5df36a09012ce8236423f5ada33bb91 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:16:38 +0200 Subject: [PATCH 2/6] remove using --- Wox.Infrastructure/Image/ImageLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 8479ae94a6..e4ff51cd5e 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; From e80147d24e2e47af4f5d22fd31c26faa06b56f7f Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:17:58 +0200 Subject: [PATCH 3/6] removed a duplicate check --- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index e4ff51cd5e..761613bc49 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -46,7 +46,7 @@ namespace Wox.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(x => + ImageCache.Usage.AsParallel().ForAll(x => { Load(x.Key); }); From 05bd32f750c4bb9f8b4f670cf661bb8b235dfe56 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 22:01:15 +0200 Subject: [PATCH 4/6] remove comment --- Wox.Infrastructure/Image/ImageLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 761613bc49..59a7a24fb4 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -174,7 +174,6 @@ namespace Wox.Infrastructure.Image public static ImageSource Load(string path, bool loadFullImage = false) { - // return LoadInternal(path, loadFullImage).ImageSource; var imageResult = LoadInternal(path, loadFullImage); var img = imageResult.ImageSource; From fd59088528481d25615579a8a32968b057c6f6c2 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 22:33:00 +0200 Subject: [PATCH 5/6] made data images freeze as well --- Wox.Infrastructure/Image/ImageLoader.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 59a7a24fb4..528900ce7c 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -100,7 +100,9 @@ namespace Wox.Infrastructure.Image if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { - return new ImageResult(new BitmapImage(new Uri(path)), ImageType.Data); + var imageSource = new BitmapImage(new Uri(path)); + imageSource.Freeze(); + return new ImageResult(imageSource, ImageType.Data); } if (!Path.IsPathRooted(path)) @@ -181,7 +183,7 @@ namespace Wox.Infrastructure.Image { // we need to get image hash string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) - { + { if (GuidToKey.TryGetValue(hash, out string key)) { // image already exists img = ImageCache[key]; @@ -195,7 +197,7 @@ namespace Wox.Infrastructure.Image // update cache ImageCache[path] = img; } - + return img; } From 28b098cfb7361ad1ab72f2bcae98ee7a53dc0fec Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 4 Jan 2020 00:40:37 +0200 Subject: [PATCH 6/6] make image created in hash freeze --- Wox.Infrastructure/Image/ImageHashGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageHashGenerator.cs b/Wox.Infrastructure/Image/ImageHashGenerator.cs index 96361d8155..9ace8b74fc 100644 --- a/Wox.Infrastructure/Image/ImageHashGenerator.cs +++ b/Wox.Infrastructure/Image/ImageHashGenerator.cs @@ -27,7 +27,9 @@ namespace Wox.Infrastructure.Image // enc2.Frames.Add(BitmapFrame.Create(tt)); var enc = new JpegBitmapEncoder(); - enc.Frames.Add(BitmapFrame.Create(image)); + var bitmapFrame = BitmapFrame.Create(image); + bitmapFrame.Freeze(); + enc.Frames.Add(bitmapFrame); enc.Save(outStream); var byteArray = outStream.GetBuffer(); using (var sha1 = new SHA1CryptoServiceProvider())