You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
9.6 KiB
297 lines
9.6 KiB
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<string> SourceUriRawProperty = AvaloniaProperty.Register< |
|
GifImage, |
|
string |
|
>("SourceUriRaw"); |
|
|
|
public static readonly StyledProperty<Uri> SourceUriProperty = AvaloniaProperty.Register<GifImage, Uri>( |
|
"SourceUri" |
|
); |
|
|
|
public static readonly StyledProperty<Stream> SourceStreamProperty = AvaloniaProperty.Register< |
|
GifImage, |
|
Stream |
|
>("SourceStream"); |
|
|
|
public static readonly StyledProperty<IterationCount> IterationCountProperty = AvaloniaProperty.Register< |
|
GifImage, |
|
IterationCount |
|
>("IterationCount", IterationCount.Infinite); |
|
|
|
private IGifInstance? _gifInstance; |
|
|
|
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty = AvaloniaProperty.Register< |
|
GifImage, |
|
StretchDirection |
|
>("StretchDirection"); |
|
|
|
public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<GifImage, Stretch>( |
|
"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()); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Measures the control. |
|
/// </summary> |
|
/// <param name="availableSize">The available size.</param> |
|
/// <returns>The desired size of the control.</returns> |
|
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; |
|
} |
|
|
|
/// <inheritdoc/> |
|
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); |
|
} |
|
} |
|
}
|
|
|