using System; using System.IO; using System.Numerics; using Avalonia; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Rendering.Composition; using Avalonia.VisualTree; namespace Avalonia.Gif { public class GifImage : Control { public static readonly StyledProperty SourceUriRawProperty = AvaloniaProperty.Register< GifImage, string >("SourceUriRaw"); public static readonly StyledProperty SourceUriProperty = AvaloniaProperty.Register( "SourceUri" ); public static readonly StyledProperty SourceStreamProperty = AvaloniaProperty.Register< GifImage, Stream >("SourceStream"); public static readonly StyledProperty IterationCountProperty = AvaloniaProperty.Register< GifImage, IterationCount >("IterationCount", IterationCount.Infinite); private IGifInstance? _gifInstance; public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register< GifImage, StretchDirection >("StretchDirection"); public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register( "Stretch" ); private CompositionCustomVisual? _customVisual; private object? _initialSource = null; protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { switch (change.Property.Name) { case nameof(SourceUriRaw): case nameof(SourceUri): case nameof(SourceStream): SourceChanged(change); break; case nameof(Stretch): case nameof(StretchDirection): InvalidateArrange(); InvalidateMeasure(); Update(); break; case nameof(IterationCount): IterationCountChanged(change); break; case nameof(Bounds): Update(); break; } base.OnPropertyChanged(change); } public string SourceUriRaw { get => GetValue(SourceUriRawProperty); set => SetValue(SourceUriRawProperty, value); } public Uri SourceUri { get => GetValue(SourceUriProperty); set => SetValue(SourceUriProperty, value); } public Stream SourceStream { get => GetValue(SourceStreamProperty); set => SetValue(SourceStreamProperty, value); } public IterationCount IterationCount { get => GetValue(IterationCountProperty); set => SetValue(IterationCountProperty, value); } public StretchDirection StretchDirection { get => GetValue(StretchDirectionProperty); set => SetValue(StretchDirectionProperty, value); } public Stretch Stretch { get => GetValue(StretchProperty); set => SetValue(StretchProperty, value); } private static void IterationCountChanged(AvaloniaPropertyChangedEventArgs e) { var image = e.Sender as GifImage; if (image is null || e.NewValue is not IterationCount iterationCount) return; image.IterationCount = iterationCount; } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { var compositor = ElementComposition.GetElementVisual(this)?.Compositor; if (compositor == null || _customVisual?.Compositor == compositor) return; _customVisual = compositor.CreateCustomVisual(new CustomVisualHandler()); ElementComposition.SetElementChildVisual(this, _customVisual); _customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage); if (_initialSource is not null) { UpdateGifInstance(_initialSource); _initialSource = null; } Update(); base.OnAttachedToVisualTree(e); } private void Update() { if (_customVisual is null || _gifInstance is null) return; var dpi = this.GetVisualRoot()?.RenderScaling ?? 1.0; var sourceSize = _gifInstance.GifPixelSize.ToSize(dpi); var viewPort = new Rect(Bounds.Size); var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection); var scaledSize = sourceSize * scale; var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort); if (Stretch == Stretch.None) { _customVisual.Size = new Vector2((float)sourceSize.Width, (float)sourceSize.Height); } else { _customVisual.Size = new Vector2((float)destRect.Size.Width, (float)destRect.Size.Height); } _customVisual.Offset = new Vector3((float)destRect.Position.X, (float)destRect.Position.Y, 0); } private class CustomVisualHandler : CompositionCustomVisualHandler { private TimeSpan _animationElapsed; private TimeSpan? _lastServerTime; private IGifInstance? _currentInstance; private bool _running; public static readonly object StopMessage = new(), StartMessage = new(); public override void OnMessage(object message) { if (message == StartMessage) { _running = true; _lastServerTime = null; RegisterForNextAnimationFrameUpdate(); } else if (message == StopMessage) { _running = false; } else if (message is IGifInstance instance) { _currentInstance?.Dispose(); _currentInstance = instance; } } public override void OnAnimationFrameUpdate() { if (!_running) return; Invalidate(); RegisterForNextAnimationFrameUpdate(); } public override void OnRender(ImmediateDrawingContext drawingContext) { if (_running) { if (_lastServerTime.HasValue) _animationElapsed += (CompositionNow - _lastServerTime.Value); _lastServerTime = CompositionNow; } try { if (_currentInstance is null || _currentInstance.IsDisposed) return; var bitmap = _currentInstance.ProcessFrameTime(_animationElapsed); if (bitmap is not null) { drawingContext.DrawBitmap( bitmap, new Rect(_currentInstance.GifPixelSize.ToSize(1)), GetRenderBounds() ); } } catch (Exception e) { Logger.Sink?.Log(LogEventLevel.Error, "GifImage Renderer ", this, e.ToString()); } } } /// /// Measures the control. /// /// The available size. /// The desired size of the control. protected override Size MeasureOverride(Size availableSize) { var result = new Size(); var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0; if (_gifInstance != null) { result = Stretch.CalculateSize( availableSize, _gifInstance.GifPixelSize.ToSize(scaling), StretchDirection ); } return result; } /// protected override Size ArrangeOverride(Size finalSize) { if (_gifInstance is null) return new Size(); var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0; var sourceSize = _gifInstance.GifPixelSize.ToSize(scaling); var result = Stretch.CalculateSize(finalSize, sourceSize); return result; } private void SourceChanged(AvaloniaPropertyChangedEventArgs e) { if ( e.NewValue is null || (e.NewValue is string value && !Uri.IsWellFormedUriString(value, UriKind.Absolute)) ) { return; } if (_customVisual is null) { _initialSource = e.NewValue; return; } UpdateGifInstance(e.NewValue); InvalidateArrange(); InvalidateMeasure(); Update(); } private void UpdateGifInstance(object source) { _gifInstance?.Dispose(); _gifInstance = new WebpInstance(source); // _gifInstance = new GifInstance(source); _gifInstance.IterationCount = IterationCount; _customVisual?.SendHandlerMessage(_gifInstance); } } }