/// <inheritdoc/>
        public async Task <string> WriteAsync(
            string destinationPath,
            IReadOnlyList <PositionAxisContainerModel <string> > images,
            string name,
            int delay,
            BezierEasingType bezierEasingType,
            CancellationToken ct)
        {
            try
            {
                var mapper = new BezierPositionMapper(images.Count(), bezierEasingType);

                var filenames = images.OrderBy(e => e.AxisType).ThenBy(e => e.Position)
                                .Select(e => e.Entity).ToArray();

                var filename = $"{name}{imageExtension}";

                await AbstractWriteAsync(destinationPath, filename, destinationPath, filenames, delay, bezierEasingType, mapper, ct);

                return(filename);
            }
            catch (OperationCanceledException e)
            {
                throw new AmiException("The writing of the GIF has been cancelled.", e);
            }
            catch (Exception e)
            {
                throw new AmiException("The GIF could not be written.", e);
            }
        }
        /// <summary>
        /// Writes the GIF images asynchronous.
        /// </summary>
        /// <param name="destinationPath">The destination path.</param>
        /// <param name="images">The images.</param>
        /// <param name="bezierEasingType">Type of the bezier easing.</param>
        /// <param name="ct">The cancellation token.</param>
        /// <returns>
        /// The output information for each axis.
        /// </returns>
        /// <exception cref="AmiException">
        /// The writing of the GIF has been cancelled.
        /// or
        /// The GIF could not be written.
        /// </exception>
        public async Task <IReadOnlyList <AxisContainerModel <string> > > WriteAsync(
            string destinationPath,
            IReadOnlyList <PositionAxisContainerModel <string> > images,
            BezierEasingType bezierEasingType,
            CancellationToken ct)
        {
            try
            {
                var result = new List <AxisContainerModel <string> >();

                foreach (var axisImages in images.GroupBy(e => e.AxisType))
                {
                    ct.ThrowIfCancellationRequested();
                    string filename = await WriteAsync(
                        destinationPath,
                        axisImages.ToList(),
                        axisImages.Key.ToString(),
                        bezierEasingType,
                        ct);

                    result.Add(new AxisContainerModel <string>(axisImages.Key, filename));
                }

                return(result);
            }
            catch (OperationCanceledException e)
            {
                throw new AmiException("The writing of the GIF has been cancelled.", e);
            }
            catch (Exception e)
            {
                throw new AmiException("The GIF could not be written.", e);
            }
        }
 /// <summary>
 /// Writes the GIF images asynchronous.
 /// </summary>
 /// <param name="destinationPath">The destination path.</param>
 /// <param name="destinationFilename">The destination filename.</param>
 /// <param name="sourcePath">The source path.</param>
 /// <param name="sourceFilenames">The source filenames.</param>
 /// <param name="delay">The delay between frames.</param>
 /// <param name="bezierEasingType">Type of the bezier easing.</param>
 /// <param name="mapper">The position mapper.</param>
 /// <param name="ct">The cancellation token.</param>
 /// <returns>
 /// A <see cref="Task" /> representing the asynchronous operation.
 /// </returns>
 protected abstract Task AbstractWriteAsync(
     string destinationPath,
     string destinationFilename,
     string sourcePath,
     string[] sourceFilenames,
     int delay,
     BezierEasingType bezierEasingType,
     BezierPositionMapper mapper,
     CancellationToken ct);
        /// <summary>
        /// Initializes a new instance of the <see cref="BezierPositionMapper"/> class.
        /// </summary>
        /// <param name="amount">The amount of positions.</param>
        /// <param name="bezierEasingType">Type of the Bézier curve easing.</param>
        public BezierPositionMapper(uint amount, BezierEasingType bezierEasingType)
            : this(amount)
        {
            var vectors = ConvertBezierEasingType(bezierEasingType);

            v1 = vectors.Item1;
            v2 = vectors.Item2;

            Init(amount);
        }
        public void BezierPositionMapper_GetPointOnBezierCurve_BezierEasingType(BezierEasingType bezierEasingType)
        {
            // Arrange
            uint amount = 60;

            // Act
            var mapper = new BezierPositionMapper(amount, bezierEasingType);

            // Assert
            Assert.AreEqual(new Tuple <float, float>(0, 0), mapper.GetPointOnBezierCurve(0));
            Assert.AreEqual(new Tuple <float, float>(1, 1), mapper.GetPointOnBezierCurve(1));

            float duration = 0;

            for (uint i = 0; i < amount; i++)
            {
                duration += mapper.GetMappedPosition(i);
            }
            Assert.IsTrue(duration > 2800f);
            Assert.IsTrue(duration < 3200f);
        }
        public void BezierPositionMapper_GetPointOnBezierCurve_BezierEasingType()
        {
            // Arrange
            uint             amount           = 60;
            BezierEasingType bezierEasingType = BezierEasingType.Linear;

            // Act
            var mapper = new BezierPositionMapper(amount, bezierEasingType);

            // Assert
            Assert.AreEqual(new Tuple <float, float>(0, 0), mapper.GetPointOnBezierCurve(0));
            Assert.AreEqual(new Tuple <float, float>(1, 1), mapper.GetPointOnBezierCurve(1));
            Assert.AreEqual(new Tuple <float, float>(0.5f, 0.5f), mapper.GetPointOnBezierCurve(0.5f));

            float duration = 0;

            for (uint i = 0; i < amount; i++)
            {
                duration += mapper.GetMappedPosition(i);
            }
            Assert.AreEqual(3000, Convert.ToInt32(duration));
        }