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..9ace8b74fc --- /dev/null +++ b/Wox.Infrastructure/Image/ImageHashGenerator.cs @@ -0,0 +1,49 @@ +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(); + var bitmapFrame = BitmapFrame.Create(image); + bitmapFrame.Freeze(); + enc.Frames.Add(bitmapFrame); + 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..528900ce7c 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -14,6 +14,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 +32,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 +46,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().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 +60,56 @@ 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)); + var imageSource = new BitmapImage(new Uri(path)); + imageSource.Freeze(); + return new ImageResult(imageSource, 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 +118,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 +146,7 @@ namespace Wox.Infrastructure.Image } else { + type = ImageType.File; image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.None); } @@ -128,17 +156,50 @@ 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) + { + 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 @@ +