/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * 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 1 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; using System.Drawing; using System.Collections.Generic; namespace GreenshotPlugin.Core { /// /// Helper interface for passing base type /// public interface IAnimator { /// /// Is there a next frame? /// bool HasNext { get; } /// /// The amount of frames /// int Frames { get; } /// /// Current frame number /// int CurrentFrameNr { get; } } /// /// This class is used to store a animation leg /// internal class AnimationLeg { public T Destination { get; set; } public int Frames { get; set; } public EasingType EasingType { get; set; } public EasingMode EasingMode { get; set; } } /// /// Base class for the animation logic, this only implements Properties and a constructor /// /// Type for the animation, like Point/Rectangle/Size public abstract class AnimatorBase : IAnimator { private readonly Queue> _queue = new Queue>(); /// /// Constructor /// /// /// /// /// /// public AnimatorBase(T first, T last, int frames, EasingType easingType, EasingMode easingMode) { First = first; Last = last; Frames = frames; Current = first; EasingType = easingType; EasingMode = easingMode; } /// /// The amount of frames /// public int Frames { get; private set; } /// /// Current frame number /// public int CurrentFrameNr { get; private set; } /// /// First animation value /// public T First { get; private set; } /// /// Last animation value, of this "leg" /// public T Last { get; private set; } /// /// Final animation value, this is including the legs /// public T Final { get { if (_queue.Count == 0) { return Last; } return _queue.ToArray()[_queue.Count - 1].Destination; } } /// /// This restarts the current animation and changes the last frame /// /// public void ChangeDestination(T newDestination) { ChangeDestination(newDestination, Frames); } /// /// This restarts the current animation and changes the last frame /// /// /// public void ChangeDestination(T newDestination, int frames) { _queue.Clear(); First = Current; CurrentFrameNr = 0; Frames = frames; Last = newDestination; } /// /// Queue the destination, it will be used to continue at the last frame /// All values will stay the same /// /// public void QueueDestinationLeg(T queuedDestination) { QueueDestinationLeg(queuedDestination, Frames, EasingType, EasingMode); } /// /// Queue the destination, it will be used to continue at the last frame /// /// /// public void QueueDestinationLeg(T queuedDestination, int frames) { QueueDestinationLeg(queuedDestination, frames, EasingType, EasingMode); } /// /// Queue the destination, it will be used to continue at the last frame /// /// /// /// EasingType public void QueueDestinationLeg(T queuedDestination, int frames, EasingType easingType) { QueueDestinationLeg(queuedDestination, frames, easingType, EasingMode); } /// /// Queue the destination, it will be used to continue at the last frame /// /// /// /// /// public void QueueDestinationLeg(T queuedDestination, int frames, EasingType easingType, EasingMode easingMode) { AnimationLeg leg = new AnimationLeg { Destination = queuedDestination, Frames = frames, EasingType = easingType, EasingMode = easingMode }; _queue.Enqueue(leg); } /// /// The EasingType to use for the animation /// public EasingType EasingType { get; set; } /// /// The EasingMode to use for the animation /// public EasingMode EasingMode { get; set; } /// /// Get the easing value, which is from 0-1 and depends on the frame /// protected double EasingValue { get => EasingMode switch { EasingMode.EaseOut => Easing.EaseOut(CurrentFrameNr / (double) Frames, EasingType), EasingMode.EaseInOut => Easing.EaseInOut(CurrentFrameNr / (double) Frames, EasingType), EasingMode.EaseIn => Easing.EaseIn(CurrentFrameNr / (double) Frames, EasingType), _ => Easing.EaseIn(CurrentFrameNr / (double) Frames, EasingType) }; } /// /// Get the current (previous) frame object /// public virtual T Current { get; set; } /// /// Returns if there are any frame left, and if this is the case than the frame is increased. /// public virtual bool NextFrame { get { if (CurrentFrameNr < Frames) { CurrentFrameNr++; return true; } if (_queue.Count > 0) { First = Current; CurrentFrameNr = 0; AnimationLeg nextLeg = _queue.Dequeue(); Last = nextLeg.Destination; Frames = nextLeg.Frames; EasingType = nextLeg.EasingType; EasingMode = nextLeg.EasingMode; return true; } return false; } } /// /// Are there more frames to animate? /// public virtual bool HasNext { get { if (CurrentFrameNr < Frames) { return true; } return _queue.Count > 0; } } /// /// Get the next animation frame value object /// /// public abstract T Next(); } /// /// Implementation of the RectangleAnimator /// public class RectangleAnimator : AnimatorBase { public RectangleAnimator(Rectangle first, Rectangle last, int frames) : base(first, last, frames, EasingType.Linear, EasingMode.EaseIn) { } public RectangleAnimator(Rectangle first, Rectangle last, int frames, EasingType easingType) : base(first, last, frames, easingType, EasingMode.EaseIn) { } public RectangleAnimator(Rectangle first, Rectangle last, int frames, EasingType easingType, EasingMode easingMode) : base(first, last, frames, easingType, easingMode) { } /// /// Calculate the next frame object /// /// Rectangle public override Rectangle Next() { if (NextFrame) { double easingValue = EasingValue; double dx = Last.X - First.X; double dy = Last.Y - First.Y; int x = First.X + (int)(easingValue * dx); int y = First.Y + (int)(easingValue * dy); double dw = Last.Width - First.Width; double dh = Last.Height - First.Height; int width = First.Width + (int)(easingValue * dw); int height = First.Height + (int)(easingValue * dh); Current = new Rectangle(x, y, width, height); } return Current; } } /// /// Implementation of the PointAnimator /// public class PointAnimator : AnimatorBase { public PointAnimator(Point first, Point last, int frames) : base(first, last, frames, EasingType.Linear, EasingMode.EaseIn) { } public PointAnimator(Point first, Point last, int frames, EasingType easingType) : base(first, last, frames, easingType, EasingMode.EaseIn) { } public PointAnimator(Point first, Point last, int frames, EasingType easingType, EasingMode easingMode) : base(first, last, frames, easingType, easingMode) { } /// /// Calculate the next frame value /// /// Point public override Point Next() { if (NextFrame) { double easingValue = EasingValue; double dx = Last.X - First.X; double dy = Last.Y - First.Y; int x = First.X + (int)(easingValue * dx); int y = First.Y + (int)(easingValue * dy); Current = new Point(x, y); } return Current; } } /// /// Implementation of the SizeAnimator /// public class SizeAnimator : AnimatorBase { public SizeAnimator(Size first, Size last, int frames) : base(first, last, frames, EasingType.Linear, EasingMode.EaseIn) { } public SizeAnimator(Size first, Size last, int frames, EasingType easingType) : base(first, last, frames, easingType, EasingMode.EaseIn) { } public SizeAnimator(Size first, Size last, int frames, EasingType easingType, EasingMode easingMode) : base(first, last, frames, easingType, easingMode) { } /// /// Calculate the next frame values /// /// Size public override Size Next() { if (NextFrame) { double easingValue = EasingValue; double dw = Last.Width - First.Width; double dh = Last.Height - First.Height; int width = First.Width + (int)(easingValue * dw); int height = First.Height + (int)(easingValue * dh); Current = new Size(width, height); } return Current; } } /// /// Implementation of the ColorAnimator /// public class ColorAnimator : AnimatorBase { public ColorAnimator(Color first, Color last, int frames) : base(first, last, frames, EasingType.Linear, EasingMode.EaseIn) { } public ColorAnimator(Color first, Color last, int frames, EasingType easingType) : base(first, last, frames, easingType, EasingMode.EaseIn) { } public ColorAnimator(Color first, Color last, int frames, EasingType easingType, EasingMode easingMode) : base(first, last, frames, easingType, easingMode) { } /// /// Calculate the next frame values /// /// Color public override Color Next() { if (NextFrame) { double easingValue = EasingValue; double da = Last.A - First.A; double dr = Last.R - First.R; double dg = Last.G - First.G; double db = Last.B - First.B; int a = First.A + (int)(easingValue * da); int r = First.R + (int)(easingValue * dr); int g = First.G + (int)(easingValue * dg); int b = First.B + (int)(easingValue * db); Current = Color.FromArgb(a,r,g,b); } return Current; } } /// /// Implementation of the IntAnimator /// public class IntAnimator : AnimatorBase { public IntAnimator(int first, int last, int frames) : base(first, last, frames, EasingType.Linear, EasingMode.EaseIn) { } public IntAnimator(int first, int last, int frames, EasingType easingType) : base(first, last, frames, easingType, EasingMode.EaseIn) { } public IntAnimator(int first, int last, int frames, EasingType easingType, EasingMode easingMode) : base(first, last, frames, easingType, easingMode) { } /// /// Calculate the next frame values /// /// int public override int Next() { if (NextFrame) { double easingValue = EasingValue; double delta = Last - First; Current = First + (int)(easingValue * delta); } return Current; } } /// /// Easing logic, to make the animations more "fluent" /// public static class Easing { // Adapted from http://www.robertpenner.com/easing/penner_chapter7_tweening.pdf public static double Ease(double linearStep, double acceleration, EasingType type) { double easedStep = acceleration > 0 ? EaseIn(linearStep, type) : acceleration < 0 ? EaseOut(linearStep, type) : linearStep; // Lerp: return ((easedStep - linearStep) * Math.Abs(acceleration) + linearStep); } public static double EaseIn(double linearStep, EasingType type) => type switch { EasingType.Step => (linearStep < 0.5 ? 0 : 1), EasingType.Linear => linearStep, EasingType.Sine => Sine.EaseIn(linearStep), EasingType.Quadratic => Power.EaseIn(linearStep, 2), EasingType.Cubic => Power.EaseIn(linearStep, 3), EasingType.Quartic => Power.EaseIn(linearStep, 4), EasingType.Quintic => Power.EaseIn(linearStep, 5), _ => throw new NotImplementedException() }; public static double EaseOut(double linearStep, EasingType type) => type switch { EasingType.Step => (linearStep < 0.5 ? 0 : 1), EasingType.Linear => linearStep, EasingType.Sine => Sine.EaseOut(linearStep), EasingType.Quadratic => Power.EaseOut(linearStep, 2), EasingType.Cubic => Power.EaseOut(linearStep, 3), EasingType.Quartic => Power.EaseOut(linearStep, 4), EasingType.Quintic => Power.EaseOut(linearStep, 5), _ => throw new NotImplementedException() }; public static double EaseInOut(double linearStep, EasingType easeInType, EasingType easeOutType) { return linearStep < 0.5 ? EaseInOut(linearStep, easeInType) : EaseInOut(linearStep, easeOutType); } public static double EaseInOut(double linearStep, EasingType type) => type switch { EasingType.Step => (linearStep < 0.5 ? 0 : 1), EasingType.Linear => linearStep, EasingType.Sine => Sine.EaseInOut(linearStep), EasingType.Quadratic => Power.EaseInOut(linearStep, 2), EasingType.Cubic => Power.EaseInOut(linearStep, 3), EasingType.Quartic => Power.EaseInOut(linearStep, 4), EasingType.Quintic => Power.EaseInOut(linearStep, 5), _ => throw new NotImplementedException() }; private static class Sine { public static double EaseIn(double s) { return Math.Sin(s * (Math.PI / 2) - (Math.PI / 2)) + 1; } public static double EaseOut(double s) { return Math.Sin(s * (Math.PI / 2)); } public static double EaseInOut(double s) { return Math.Sin(s * Math.PI - (Math.PI / 2) + 1) / 2; } } private static class Power { public static double EaseIn(double s, int power) { return Math.Pow(s, power); } public static double EaseOut(double s, int power) { var sign = power % 2 == 0 ? -1 : 1; return sign * (Math.Pow(s - 1, power) + sign); } public static double EaseInOut(double s, int power) { s *= 2; if (s < 1) { return EaseIn(s, power) / 2; } var sign = power % 2 == 0 ? -1 : 1; return (sign / 2.0 * (Math.Pow(s - 2, power) + sign * 2)); } } } /// /// This defines the way the animation works /// public enum EasingType { Step, Linear, Sine, Quadratic, Cubic, Quartic, Quintic } public enum EasingMode { EaseIn, EaseOut, EaseInOut } }