/// <summary>
        /// Updates position until stopDragging completes.
        /// </summary>
        public static async Task <IBehavior> DragRelative(this IBehavior self,
                                                          V2d initialPosition, IEvent <V2d> moves, IAwaitable stopDragging, CancellationToken ct
                                                          )
        {
            var obj = self as IBehaviorTransform2d;

            if (obj == null)
            {
                throw new InvalidOperationException("DragRelative requires IBehaviorTransform2d.");
            }

            var lastPos = initialPosition;
            await stopDragging.RepeatUntilCompleted(
                async delegate
            {
                var p = await moves.Next.WithCancellation(ct);
                obj.Transform(M33d.Translation(p - lastPos));
                lastPos = p;
            });

            return(self);
        }
 /// <summary>
 /// Returns a Matrix to
 /// convert from pixel-center position to normalized image position [0,1][0,1].
 /// (The inverse of toPixelCenter.)
 /// </summary>
 public static M33d PixelCenterToNormalizedImgMat(V2d imgSizeInPixel)
 {
     return(M33d.Scale(V2d.II / imgSizeInPixel) * M33d.Translation(V2dHalf));
 }
 /// <summary>
 /// Returns a Matrix to
 /// convert from normalized image position [0,1][0,1] to pixel-center position
 /// (The inverse of toNormalizedImgPos.)
 /// </summary>
 public static M33d NormalizedImagePosToPixelCenterMat(V2d imgSizeInPixel)
 {
     return(M33d.Translation(-V2dHalf) * M33d.Scale(imgSizeInPixel));
 }
 public static Trafo2d Translation(double dx, double dy)
 {
     return(new Trafo2d(M33d.Translation(dx, dy),
                        M33d.Translation(-dx, -dy)));
 }
 public static Trafo2d Translation(V2d v)
 {
     return(new Trafo2d(M33d.Translation(v),
                        M33d.Translation(-v)));
 }