private static bool ComputeDecomposedTransform(TransformOperations operations, int startOffset, out Matrix.Decomposed decomposed)
        {
            Matrix transform = operations.ApplyTransforms(startOffset);

            if (!Matrix.TryDecomposeTransform(transform, out decomposed))
            {
                return(false);
            }

            return(true);
        }
        private static bool TryInterpolate(TransformOperations from, TransformOperations to, double progress, ref TransformOperations result)
        {
            bool fromIdentity = from.IsIdentity;
            bool toIdentity   = to.IsIdentity;

            if (fromIdentity && toIdentity)
            {
                return(true);
            }

            int matchingPrefixLength = ComputeMatchingPrefixLength(from, to);
            int fromSize             = fromIdentity ? 0 : from._operations.Count;
            int toSize        = toIdentity ? 0 : to._operations.Count;
            int numOperations = Math.Max(fromSize, toSize);

            var builder = new Builder(matchingPrefixLength);

            for (int i = 0; i < matchingPrefixLength; i++)
            {
                TransformOperation interpolated = new TransformOperation
                {
                    Type = TransformOperation.OperationType.Identity
                };

                if (!TransformOperation.TryInterpolate(
                        i >= fromSize ? default(TransformOperation?) : from._operations[i],
                        i >= toSize ? default(TransformOperation?) : to._operations[i],
                        progress,
                        ref interpolated))
                {
                    return(false);
                }

                builder.Append(interpolated);
            }

            if (matchingPrefixLength < numOperations)
            {
                if (!ComputeDecomposedTransform(from, matchingPrefixLength, out Matrix.Decomposed fromDecomposed) ||
                    !ComputeDecomposedTransform(to, matchingPrefixLength, out Matrix.Decomposed toDecomposed))
                {
                    return(false);
                }

                var transform = InterpolationUtilities.InterpolateDecomposedTransforms(ref fromDecomposed, ref toDecomposed, progress);

                builder.AppendMatrix(InterpolationUtilities.ComposeTransform(transform));
            }

            result = builder.Build();

            return(true);
        }
        public static TransformOperations Interpolate(TransformOperations from, TransformOperations to, double progress)
        {
            TransformOperations result = Identity;

            if (!TryInterpolate(from, to, progress, ref result))
            {
                // If the matrices cannot be interpolated, fallback to discrete animation logic.
                // See https://drafts.csswg.org/css-transforms/#matrix-interpolation
                result = progress < 0.5 ? from : to;
            }

            return(result);
        }
        private static int ComputeMatchingPrefixLength(TransformOperations from, TransformOperations to)
        {
            int numOperations = Math.Min(from._operations.Count, to._operations.Count);

            for (int i = 0; i < numOperations; i++)
            {
                if (from._operations[i].Type != to._operations[i].Type)
                {
                    return(i);
                }
            }

            // If the operations match to the length of the shorter list, then pad its
            // length with the matching identity operations.
            // https://drafts.csswg.org/css-transforms/#transform-function-lists
            return(Math.Max(from._operations.Count, to._operations.Count));
        }