Use my home-made GIF and APNG animator. It's really save CPU compared to previous one!

This commit is contained in:
Paddy Xu
2017-07-31 00:14:21 +03:00
parent 071a3b4f22
commit 2f12127cc9
12 changed files with 658 additions and 33 deletions

View File

@@ -0,0 +1,108 @@
// Copyright © 2017 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 <http://www.gnu.org/licenses/>.
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using LibAPNG;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage
{
internal class APNGAnimationProvider : IAnimationProvider
{
public void GetAnimator(ObjectAnimationUsingKeyFrames animator, string path)
{
var decoder = new APNGBitmap(path);
if (decoder.IsSimplePNG)
{
animator.KeyFrames.Add(
new DiscreteObjectKeyFrame(decoder.DefaultImage.GetBitmapSource(), TimeSpan.Zero));
animator.Duration = Duration.Forever;
return;
}
var clock = TimeSpan.Zero;
var header = decoder.IHDRChunk;
Frame prevFrame = null;
BitmapSource prevRenderedFrame = null;
foreach (var rawFrame in decoder.Frames)
{
var frame = MakeFrame(header, rawFrame, prevFrame, prevRenderedFrame);
prevFrame = rawFrame;
prevRenderedFrame = frame;
var delay = TimeSpan.FromSeconds(
(double) rawFrame.fcTLChunk.DelayNum /
(rawFrame.fcTLChunk.DelayDen == 0 ? 100 : rawFrame.fcTLChunk.DelayDen));
animator.KeyFrames.Add(new DiscreteObjectKeyFrame(frame, clock));
clock += delay;
}
animator.Duration = clock;
animator.RepeatBehavior = RepeatBehavior.Forever;
}
private static BitmapSource MakeFrame(IHDRChunk header, Frame rawFrame, Frame previousFrame,
BitmapSource previousRenderedFrame)
{
var visual = new DrawingVisual();
using (var context = visual.RenderOpen())
{
switch (rawFrame.fcTLChunk.DisposeOp)
{
case DisposeOps.APNGDisposeOpNone:
// restore previousRenderedFrame
//if (previousRenderedFrame != null)
//{
// var fullRect = new Rect(0, 0, header.Width, header.Height);
// context.DrawImage(previousRenderedFrame, fullRect);
//}
break;
case DisposeOps.APNGDisposeOpPrevious:
// restore previousFrame
if (previousFrame != null)
{
var pFrameRect = new Rect(previousFrame.fcTLChunk.XOffset,
previousFrame.fcTLChunk.YOffset,
previousFrame.fcTLChunk.Width, previousFrame.fcTLChunk.Height);
context.DrawImage(previousFrame.GetBitmapSource(), pFrameRect);
}
break;
case DisposeOps.APNGDisposeOpBackground:
// do nothing
break;
}
// draw current frame
var frameRect = new Rect(rawFrame.fcTLChunk.XOffset, rawFrame.fcTLChunk.YOffset,
rawFrame.fcTLChunk.Width, rawFrame.fcTLChunk.Height);
context.DrawImage(rawFrame.GetBitmapSource(), frameRect);
}
var bitmap = new RenderTargetBitmap(
header.Width, header.Height,
96, 96,
PixelFormats.Pbgra32);
bitmap.Render(visual);
return bitmap;
}
}
}

View File

@@ -0,0 +1,76 @@
// Copyright © 2017 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 <http://www.gnu.org/licenses/>.
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage
{
public class AnimatedImage : Image, IDisposable
{
public static readonly DependencyProperty AnimationUriProperty =
DependencyProperty.Register("AnimationUri", typeof(Uri), typeof(AnimatedImage),
new UIPropertyMetadata(null, LoadImage));
private readonly ObjectAnimationUsingKeyFrames _animator = new ObjectAnimationUsingKeyFrames();
public Uri AnimationUri
{
get => (Uri) GetValue(AnimationUriProperty);
set => SetValue(AnimationUriProperty, value);
}
public void Dispose()
{
BeginAnimation(SourceProperty, null);
Source = null;
_animator.KeyFrames.Clear();
}
private static void LoadImage(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
{
var instance = obj as AnimatedImage;
if (instance == null)
return;
var path = ((Uri) ev.NewValue).LocalPath;
var ext = Path.GetExtension(path).ToLower();
IAnimationProvider provider;
switch (ext)
{
case ".gif":
provider = new GIFAnimationProvider();
break;
case ".png":
provider = new APNGAnimationProvider();
break;
default:
provider = new ImageMagickProvider();
break;
}
provider.GetAnimator(instance._animator, path);
instance.BeginAnimation(SourceProperty, instance._animator);
}
}
}

View File

@@ -0,0 +1,182 @@
// Copyright © 2017 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 <http://www.gnu.org/licenses/>.
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage
{
internal class GIFAnimationProvider : IAnimationProvider
{
public void GetAnimator(ObjectAnimationUsingKeyFrames animator, string path)
{
var decoder =
new GifBitmapDecoder(new Uri(path), BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
var clock = TimeSpan.Zero;
BitmapSource prevFrame = null;
FrameInfo prevInfo = null;
foreach (var rawFrame in decoder.Frames)
{
var info = GetFrameInfo(rawFrame);
var frame = MakeFrame(
decoder.Frames[0],
rawFrame, info,
prevFrame, prevInfo);
prevFrame = frame;
prevInfo = info;
animator.KeyFrames.Add(new DiscreteObjectKeyFrame(frame, clock));
clock += info.Delay;
}
animator.Duration = clock;
animator.RepeatBehavior = RepeatBehavior.Forever;
}
#region private methods
private static BitmapSource MakeFrame(
BitmapSource fullImage,
BitmapSource rawFrame, FrameInfo frameInfo,
BitmapSource previousFrame, FrameInfo previousFrameInfo)
{
var visual = new DrawingVisual();
using (var context = visual.RenderOpen())
{
if (previousFrameInfo != null && previousFrame != null &&
previousFrameInfo.DisposalMethod == FrameDisposalMethod.Combine)
{
var fullRect = new Rect(0, 0, fullImage.PixelWidth, fullImage.PixelHeight);
context.DrawImage(previousFrame, fullRect);
}
context.DrawImage(rawFrame, frameInfo.Rect);
}
var bitmap = new RenderTargetBitmap(
fullImage.PixelWidth, fullImage.PixelHeight,
fullImage.DpiX, fullImage.DpiY,
PixelFormats.Pbgra32);
bitmap.Render(visual);
return bitmap;
}
private static FrameInfo GetFrameInfo(BitmapFrame frame)
{
var frameInfo = new FrameInfo
{
Delay = TimeSpan.FromMilliseconds(100),
DisposalMethod = FrameDisposalMethod.Replace,
Width = frame.PixelWidth,
Height = frame.PixelHeight,
Left = 0,
Top = 0
};
try
{
if (frame.Metadata is BitmapMetadata metadata)
{
const string delayQuery = "/grctlext/Delay";
const string disposalQuery = "/grctlext/Disposal";
const string widthQuery = "/imgdesc/Width";
const string heightQuery = "/imgdesc/Height";
const string leftQuery = "/imgdesc/Left";
const string topQuery = "/imgdesc/Top";
var delay = metadata.GetQueryOrNull<ushort>(delayQuery);
if (delay.HasValue)
frameInfo.Delay = TimeSpan.FromMilliseconds(10 * delay.Value);
var disposal = metadata.GetQueryOrNull<byte>(disposalQuery);
if (disposal.HasValue)
frameInfo.DisposalMethod = (FrameDisposalMethod) disposal.Value;
var width = metadata.GetQueryOrNull<ushort>(widthQuery);
if (width.HasValue)
frameInfo.Width = width.Value;
var height = metadata.GetQueryOrNull<ushort>(heightQuery);
if (height.HasValue)
frameInfo.Height = height.Value;
var left = metadata.GetQueryOrNull<ushort>(leftQuery);
if (left.HasValue)
frameInfo.Left = left.Value;
var top = metadata.GetQueryOrNull<ushort>(topQuery);
if (top.HasValue)
frameInfo.Top = top.Value;
}
}
catch (NotSupportedException)
{
}
return frameInfo;
}
#endregion
#region structs
private class FrameInfo
{
public TimeSpan Delay { get; set; }
public FrameDisposalMethod DisposalMethod { get; set; }
public double Width { private get; set; }
public double Height { private get; set; }
public double Left { private get; set; }
public double Top { private get; set; }
public Rect Rect => new Rect(Left, Top, Width, Height);
}
private enum FrameDisposalMethod
{
Replace = 0,
Combine = 1,
RestoreBackground = 2,
RestorePrevious = 3
}
#endregion
}
#region extensions
public static class Extensions
{
public static T? GetQueryOrNull<T>(this BitmapMetadata metadata, string query)
where T : struct
{
if (metadata.ContainsQuery(query))
{
var value = metadata.GetQuery(query);
if (value != null)
return (T) value;
}
return null;
}
}
#endregion
}

View File

@@ -0,0 +1,26 @@
// Copyright © 2017 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 <http://www.gnu.org/licenses/>.
using System.Windows.Media.Animation;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage
{
internal interface IAnimationProvider
{
void GetAnimator(ObjectAnimationUsingKeyFrames animator, string path);
}
}

View File

@@ -0,0 +1,51 @@
// Copyright © 2017 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 <http://www.gnu.org/licenses/>.
using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Media.Animation;
using ImageMagick;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage
{
internal class ImageMagickProvider : IAnimationProvider
{
public void GetAnimator(ObjectAnimationUsingKeyFrames animator, string path)
{
// set dcraw.exe for Magick.NET
Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
using (var image = new MagickImage(path))
{
image.Rotate(image.Orientation == OrientationType.RightTop
? 90
: image.Orientation == OrientationType.BottomRight
? 180
: image.Orientation == OrientationType.LeftBotom
? 270
: 0);
animator.KeyFrames.Add(new DiscreteObjectKeyFrame(image.ToBitmapSource(), TimeSpan.Zero));
animator.Duration = Duration.Forever;
}
Directory.SetCurrentDirectory(App.AppPath);
}
}
}