diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/APNGAnimationProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/APNGAnimationProvider.cs index e49e56a..3ebdebe 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/APNGAnimationProvider.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/APNGAnimationProvider.cs @@ -37,7 +37,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage private int _lastEffectivePreviousPreviousFrameIndex; private NativeImageProvider _nativeImageProvider; - public APngAnimationProvider(string path, NConvert meta) : base(path, meta) + public APngAnimationProvider(string path, MetaProvider meta) : base(path, meta) { if (!IsAnimatedPng(path)) { @@ -68,12 +68,18 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage } } - public override Task GetThumbnail(Size size, Size fullSize) + public override Task GetThumbnail(Size renderSize) { if (_nativeImageProvider != null) - return _nativeImageProvider.GetThumbnail(size, fullSize); + return _nativeImageProvider.GetThumbnail(renderSize); - return new Task(() => _baseFrame.GetBitmapSource()); + return new Task(() => + { + var bs = _baseFrame.GetBitmapSource(); + + bs.Freeze(); + return bs; + }); } public override Task GetRenderedFrame(int index) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimatedImage.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimatedImage.cs index a9070df..b11bb80 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimatedImage.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimatedImage.cs @@ -48,7 +48,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage public event EventHandler ImageLoaded; public event EventHandler DoZoomToFit; - private static AnimationProvider LoadFullImageCore(Uri path, NConvert meta) + private static AnimationProvider InitAnimationProvider(Uri path, MetaProvider meta) { var ext = Path.GetExtension(path.LocalPath).ToLower(); var type = Providers.First(p => p.Key.Contains(ext) || p.Key.Contains("*")).Value; @@ -69,7 +69,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage new UIPropertyMetadata(null, AnimationUriChanged)); public static readonly DependencyProperty MetaProperty = - DependencyProperty.Register("Meta", typeof(NConvert), typeof(AnimatedImage)); + DependencyProperty.Register("Meta", typeof(MetaProvider), typeof(AnimatedImage)); public static readonly DependencyProperty ContextObjectProperty = DependencyProperty.Register("ContextObject", typeof(ContextObject), typeof(AnimatedImage)); @@ -86,9 +86,9 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage set => SetValue(AnimationUriProperty, value); } - public NConvert Meta + public MetaProvider Meta { - private get => (NConvert) GetValue(MetaProperty); + private get => (MetaProvider) GetValue(MetaProperty); set => SetValue(MetaProperty, value); } @@ -106,13 +106,13 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage //var thumbnail = instance.Meta?.GetThumbnail(true); //instance.Source = thumbnail; - instance._animation = LoadFullImageCore((Uri) ev.NewValue, instance.Meta); + instance._animation = InitAnimationProvider((Uri) ev.NewValue, instance.Meta); ShowThumbnailAndStartAnimation(instance); } private static void ShowThumbnailAndStartAnimation(AnimatedImage instance) { - var task = instance._animation.GetThumbnail(instance.ContextObject.PreferredSize, instance.Meta.GetSize()); + var task = instance._animation.GetThumbnail(instance.ContextObject.PreferredSize); if (task == null) return; task.ContinueWith(_ => instance.Dispatcher.Invoke(() => @@ -121,8 +121,12 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage return; instance.Source = _.Result; - instance.DoZoomToFit?.Invoke(instance, new EventArgs()); - instance.ImageLoaded?.Invoke(instance, new EventArgs()); + + if (_.Result != null) + { + instance.DoZoomToFit?.Invoke(instance, new EventArgs()); + instance.ImageLoaded?.Invoke(instance, new EventArgs()); + } instance.BeginAnimation(AnimationFrameIndexProperty, instance._animation?.Animator); })); @@ -141,8 +145,18 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage task.ContinueWith(_ => instance.Dispatcher.Invoke(() => { - if (!instance._disposing) - instance.Source = _.Result; + if (instance._disposing) + return; + + var firstLoad = instance.Source == null; + + instance.Source = _.Result; + + if (firstLoad) + { + instance.DoZoomToFit?.Invoke(instance, new EventArgs()); + instance.ImageLoaded?.Invoke(instance, new EventArgs()); + } })); task.Start(); } diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimationProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimationProvider.cs index e79cbe4..13cf539 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimationProvider.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/AnimationProvider.cs @@ -25,7 +25,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage { internal abstract class AnimationProvider : IDisposable { - protected AnimationProvider(string path, NConvert meta) + protected AnimationProvider(string path, MetaProvider meta) { Path = path; Meta = meta; @@ -33,13 +33,13 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage public string Path { get; } - public NConvert Meta { get; } + public MetaProvider Meta { get; } public Int32AnimationUsingKeyFrames Animator { get; protected set; } public abstract void Dispose(); - public abstract Task GetThumbnail(Size size, Size fullSize); + public abstract Task GetThumbnail(Size renderSize); public abstract Task GetRenderedFrame(int index); } diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/GifAnimationProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/GifAnimationProvider.cs index 50b92cf..56ae412 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/GifAnimationProvider.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/GifAnimationProvider.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using QuickLook.Common.ExtensionMethods; +using QuickLook.Common.Helpers; using Size = System.Windows.Size; namespace QuickLook.Plugin.ImageViewer.AnimatedImage @@ -31,10 +32,13 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage private BitmapSource _frame; private bool _isPlaying; - public GifAnimationProvider(string path, NConvert meta) : base(path, meta) + public GifAnimationProvider(string path, MetaProvider meta) : base(path, meta) { _fileHandle = (Bitmap) Image.FromFile(path); + _fileHandle.SetResolution(DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Horizontal, + DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Vertical); + Animator = new Int32AnimationUsingKeyFrames {RepeatBehavior = RepeatBehavior.Forever}; Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)))); Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(10)))); @@ -53,7 +57,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage _frame = null; } - public override Task GetThumbnail(Size size, Size fullSize) + public override Task GetThumbnail(Size renderSize) { return new Task(() => { diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NConvertImageProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/ImageMagickProvider.cs similarity index 59% rename from QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NConvertImageProvider.cs rename to QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/ImageMagickProvider.cs index 5917bff..c1d61f0 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NConvertImageProvider.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/ImageMagickProvider.cs @@ -1,4 +1,4 @@ -// Copyright © 2018 Paddy Xu +// Copyright © 2020 Paddy Xu // // This file is part of QuickLook program. // @@ -16,49 +16,53 @@ // along with this program. If not, see . using System; +using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; +using ImageMagick; using QuickLook.Common.Helpers; namespace QuickLook.Plugin.ImageViewer.AnimatedImage { - internal class NConvertImageProvider : AnimationProvider + internal class ImageMagickProvider : AnimationProvider { - public NConvertImageProvider(string path, NConvert meta) : base(path, meta) + public ImageMagickProvider(string path, MetaProvider meta) : base(path, meta) { Animator = new Int32AnimationUsingKeyFrames(); Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.Zero))); } - public override Task GetThumbnail(Size size, Size fullSize) + public override Task GetThumbnail(Size renderSize) { - var decodeWidth = (int) Math.Round(fullSize.Width * - Math.Min(size.Width / 2 / fullSize.Width, - size.Height / 2 / fullSize.Height)); - var decodeHeight = (int) Math.Round(fullSize.Height / fullSize.Width * decodeWidth); + var fullSize = Meta.GetSize(); return new Task(() => { try { - using (var ms = Meta.GetTiffStream(true)) + using (var buffer = new MemoryStream(Meta.GetThumbnail())) { + if (buffer.Length == 0) + return null; + var img = new BitmapImage(); img.BeginInit(); - img.StreamSource = ms; + img.StreamSource = buffer; img.CacheOption = BitmapCacheOption.OnLoad; - img.DecodePixelWidth = decodeWidth; - img.DecodePixelHeight = decodeHeight; // specific size to avoid .net's double to int conversion + //// specific renderSize to avoid .net's double to int conversion + //img.DecodePixelWidth = Math.Max(1, (int) Math.Floor(renderSize.Width)); + //img.DecodePixelHeight = Math.Max(1, (int) Math.Floor(renderSize.Height)); img.EndInit(); var scaled = new TransformedBitmap(img, new ScaleTransform(fullSize.Width / img.PixelWidth, fullSize.Height / img.PixelHeight)); - scaled.Freeze(); + Helper.DpiHack(scaled); + scaled.Freeze(); return scaled; } } @@ -72,17 +76,27 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage public override Task GetRenderedFrame(int index) { + var fullSize = Meta.GetSize(); + return new Task(() => { try { - using (var ms = Meta.GetTiffStream(false)) + using (var mi = new MagickImage(Path)) { - var img = new BitmapImage(); - img.BeginInit(); - img.StreamSource = ms; - img.CacheOption = BitmapCacheOption.OnLoad; - img.EndInit(); + var profile = mi.GetColorProfile(); + if (profile?.Description != null && !profile.Description.Contains("sRGB")) + mi.SetProfile(ColorProfile.SRGB); + + mi.AutoOrient(); + + if (mi.Width != (int) fullSize.Width || mi.Height != (int) fullSize.Height) + mi.Resize((int) fullSize.Width, (int) fullSize.Height); + + mi.Density = new Density(DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Horizontal, + DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Vertical); + + var img = mi.ToBitmapSource(BitmapDensity.Use); img.Freeze(); return img; diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NativeImageProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NativeImageProvider.cs index 734a494..4d1cba2 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NativeImageProvider.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/AnimatedImage/NativeImageProvider.cs @@ -27,19 +27,25 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage { internal class NativeImageProvider : AnimationProvider { - public NativeImageProvider(string path, NConvert meta) : base(path, meta) + public NativeImageProvider(string path, MetaProvider meta) : base(path, meta) { Animator = new Int32AnimationUsingKeyFrames(); Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.Zero))); } - public override Task GetThumbnail(Size size, Size fullSize) + public override Task GetThumbnail(Size renderSize) { - var decodeWidth = (int) Math.Round(fullSize.Width * - Math.Min(size.Width / 2 / fullSize.Width, - size.Height / 2 / fullSize.Height)); - var decodeHeight = (int) Math.Round(fullSize.Height / fullSize.Width * decodeWidth); + var fullSize = Meta.GetSize(); + + //var decodeWidth = (int) Math.Round(fullSize.Width * + // Math.Min(renderSize.Width / 2 / fullSize.Width, + // renderSize.Height / 2 / fullSize.Height)); + //var decodeHeight = (int) Math.Round(fullSize.Height / fullSize.Width * decodeWidth); + var decodeWidth = + (int) Math.Round(Math.Min(Meta.GetSize().Width, Math.Max(1d, Math.Floor(renderSize.Width)))); + var decodeHeight = + (int) Math.Round(Math.Min(Meta.GetSize().Height, Math.Max(1d, Math.Floor(renderSize.Height)))); var orientation = Meta.GetOrientation(); var rotate = ShouldRotate(orientation); @@ -51,18 +57,22 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage img.BeginInit(); img.UriSource = new Uri(Path); img.CacheOption = BitmapCacheOption.OnLoad; + // specific renderSize to avoid .net's double to int conversion img.DecodePixelWidth = rotate ? decodeHeight : decodeWidth; - img.DecodePixelHeight = rotate ? decodeWidth : decodeHeight; // specific size to avoid .net's double to int conversion + img.DecodePixelHeight = rotate ? decodeWidth : decodeHeight; img.EndInit(); - var scaled = rotate ? - new TransformedBitmap(img, + var scaled = rotate + ? new TransformedBitmap(img, new ScaleTransform(fullSize.Height / img.PixelWidth, fullSize.Width / img.PixelHeight)) : new TransformedBitmap(img, new ScaleTransform(fullSize.Width / img.PixelWidth, fullSize.Height / img.PixelHeight)); var rotated = ApplyTransformFromExif(scaled, orientation); + + Helper.DpiHack(rotated); rotated.Freeze(); + return rotated; } catch (Exception e) @@ -75,6 +85,9 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage public override Task GetRenderedFrame(int index) { + var fullSize = Meta.GetSize(); + var rotate = ShouldRotate(Meta.GetOrientation()); + return new Task(() => { try @@ -83,9 +96,13 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage img.BeginInit(); img.UriSource = new Uri(Path); img.CacheOption = BitmapCacheOption.OnLoad; + img.DecodePixelWidth = (int) (rotate ? fullSize.Height : fullSize.Width); + img.DecodePixelHeight = (int) (rotate ? fullSize.Width : fullSize.Height); img.EndInit(); var img2 = ApplyTransformFromExif(img, Meta.GetOrientation()); + + Helper.DpiHack(img2); img2.Freeze(); return img2; @@ -100,13 +117,13 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage private static bool ShouldRotate(Orientation orientation) { - bool rotate = false; + var rotate = false; switch (orientation) { case Orientation.LeftTop: case Orientation.RightTop: case Orientation.RightBottom: - case Orientation.Leftbottom: + case Orientation.LeftBottom: rotate = true; break; } @@ -137,7 +154,7 @@ namespace QuickLook.Plugin.ImageViewer.AnimatedImage return new TransformedBitmap( new TransformedBitmap(image, new RotateTransform(270)), new ScaleTransform(-1, 1, 0, 0)); - case Orientation.Leftbottom: + case Orientation.LeftBottom: return new TransformedBitmap(image, new RotateTransform(270)); } diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Helper.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Helper.cs new file mode 100644 index 0000000..ca6b80d --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Helper.cs @@ -0,0 +1,41 @@ +// Copyright © 2020 Paddy Xu +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Reflection; +using System.Windows.Media.Imaging; +using QuickLook.Common.Helpers; + +namespace QuickLook.Plugin.ImageViewer +{ + internal class Helper + { + public static void DpiHack(BitmapSource img) + { + // a dirty hack... but is the fastest + + var newDpiX = (double) DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Horizontal; + var newDpiY = (double) DpiHelper.DefaultDpi * DpiHelper.GetCurrentScaleFactor().Vertical; + + var dpiX = img.GetType().GetField("_dpiX", + BindingFlags.NonPublic | BindingFlags.Instance); + var dpiY = img.GetType().GetField("_dpiY", + BindingFlags.NonPublic | BindingFlags.Instance); + dpiX?.SetValue(img, newDpiX); + dpiY?.SetValue(img, newDpiY); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml index babe3b9..66c1e00 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml @@ -76,23 +76,37 @@ -