using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media; using System; namespace Avalar.Views.Image { public class ImageControl : UserControl { private const double ZoomTick = 0.1; private double CurrentZoom { get; set; } = 1.0; private double InvCurrentZoom => 1 / CurrentZoom; private ImageBrush Image { get; } private Canvas ImageCanvas { get; } public ImageControl() { InitializeComponent(); Image = this.FindResource("ImageBrush") as ImageBrush ?? throw new System.ArgumentException("ImageBrush"); ImageCanvas = this.FindControl("ImageCanvas"); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } private Point MousePointerInImageSpace(Point mouseCoordinates) { var imageSize = Image.Source.Size; var canvasSize = new Size(ImageCanvas.Bounds.Width, ImageCanvas.Bounds.Height); double focalPointX; double focalPointY; if (canvasSize.AspectRatio > imageSize.AspectRatio) { focalPointY = mouseCoordinates.Y / canvasSize.Height; var halfUnusedX = (canvasSize.AspectRatio - imageSize.AspectRatio) * canvasSize.Width / 2; var clampedX = Math.Clamp(mouseCoordinates.X - halfUnusedX, 0, imageSize.Width); focalPointX = clampedX / imageSize.Width; } else { focalPointX = mouseCoordinates.X / canvasSize.Width; var halfUnusedY = 1 / (canvasSize.AspectRatio - imageSize.AspectRatio) * canvasSize.Height / 2; var clampedY = Math.Clamp(mouseCoordinates.Y - halfUnusedY, 0, imageSize.Height); focalPointY = clampedY / imageSize.Height; } return new Point(focalPointX, focalPointY); } private Point TransformToSourceRectSpace(Point from) { var sourceRectPosition = Image.SourceRect.Rect.Position; var sourceRectSize = Image.SourceRect.Rect.Size; return new Point( from.X * sourceRectSize.Width + sourceRectPosition.X, from.Y * sourceRectSize.Height + sourceRectPosition.Y ); } public void OnPointerWheelChanged(object sender, PointerWheelEventArgs e) { var invOldZoom = InvCurrentZoom; var zoomFactor = e.Delta.Y * ZoomTick + 1.0; CurrentZoom = Math.Clamp(CurrentZoom * zoomFactor, 1, double.PositiveInfinity); var sourceRect = Image.SourceRect.Rect; var canvasMouseCoordinates = e.GetPosition(ImageCanvas); var currentCenter = sourceRect.Center; var desiredCenterInImageCoords = MousePointerInImageSpace(canvasMouseCoordinates); if(e.Delta.Y < 0) { desiredCenterInImageCoords = new Point(1 - desiredCenterInImageCoords.X, 1 - desiredCenterInImageCoords.Y); } var desiredCenter = TransformToSourceRectSpace(desiredCenterInImageCoords); var maxTravelDistance = Math.Abs(invOldZoom - InvCurrentZoom); var newCenterX = Math.Clamp(desiredCenter.X, currentCenter.X - maxTravelDistance, currentCenter.X + maxTravelDistance); var newCenterY = Math.Clamp(desiredCenter.Y, currentCenter.Y - maxTravelDistance, currentCenter.Y + maxTravelDistance); var sourceRectNewTopLeft = new Point(newCenterX - InvCurrentZoom / 2, newCenterY - InvCurrentZoom / 2); Image.SourceRect = new RelativeRect(sourceRectNewTopLeft, new Size(InvCurrentZoom, InvCurrentZoom), RelativeUnit.Relative); } } }