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.
147 lines
4.6 KiB
147 lines
4.6 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Threading; |
|
using Avalonia; |
|
using Avalonia.Animation; |
|
using Avalonia.Gif.Decoding; |
|
using Avalonia.Media.Imaging; |
|
using Avalonia.Platform; |
|
|
|
namespace Avalonia.Gif |
|
{ |
|
public class GifInstance : IGifInstance |
|
{ |
|
public IterationCount IterationCount { get; set; } |
|
public bool AutoStart { get; private set; } = true; |
|
private readonly GifDecoder _gifDecoder; |
|
private readonly WriteableBitmap? _targetBitmap; |
|
private TimeSpan _totalTime; |
|
private readonly List<TimeSpan> _frameTimes; |
|
private uint _iterationCount; |
|
private int _currentFrameIndex; |
|
private readonly List<ulong> _colorTableIdList; |
|
|
|
public CancellationTokenSource CurrentCts { get; } |
|
|
|
internal GifInstance(object newValue) |
|
: this( |
|
newValue switch |
|
{ |
|
Stream s => s, |
|
Uri u => GetStreamFromUri(u), |
|
string str => GetStreamFromString(str), |
|
_ => throw new InvalidDataException("Unsupported source object") |
|
} |
|
) { } |
|
|
|
public GifInstance(string uri) |
|
: this(GetStreamFromString(uri)) { } |
|
|
|
public GifInstance(Uri uri) |
|
: this(GetStreamFromUri(uri)) { } |
|
|
|
public GifInstance(Stream currentStream) |
|
{ |
|
if (!currentStream.CanSeek) |
|
throw new InvalidDataException("The provided stream is not seekable."); |
|
|
|
if (!currentStream.CanRead) |
|
throw new InvalidOperationException("Can't read the stream provided."); |
|
|
|
currentStream.Seek(0, SeekOrigin.Begin); |
|
|
|
CurrentCts = new CancellationTokenSource(); |
|
|
|
_gifDecoder = new GifDecoder(currentStream, CurrentCts.Token); |
|
var pixSize = new PixelSize(_gifDecoder.Header.Dimensions.Width, _gifDecoder.Header.Dimensions.Height); |
|
|
|
_targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque); |
|
GifPixelSize = pixSize; |
|
|
|
_totalTime = TimeSpan.Zero; |
|
|
|
_frameTimes = _gifDecoder |
|
.Frames |
|
.Select(frame => |
|
{ |
|
_totalTime = _totalTime.Add(frame.FrameDelay); |
|
return _totalTime; |
|
}) |
|
.ToList(); |
|
|
|
_gifDecoder.RenderFrame(0, _targetBitmap); |
|
} |
|
|
|
private static Stream GetStreamFromString(string str) |
|
{ |
|
if (!Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var res)) |
|
{ |
|
throw new InvalidCastException("The string provided can't be converted to URI."); |
|
} |
|
|
|
return GetStreamFromUri(res); |
|
} |
|
|
|
private static Stream GetStreamFromUri(Uri uri) |
|
{ |
|
var uriString = uri.OriginalString.Trim(); |
|
|
|
if (!uriString.StartsWith("resm") && !uriString.StartsWith("avares")) |
|
{ |
|
return new FileStream(uriString, FileMode.Open, FileAccess.Read); |
|
} |
|
|
|
return AssetLoader.Open(uri); |
|
} |
|
|
|
public int GifFrameCount => _frameTimes.Count; |
|
|
|
public PixelSize GifPixelSize { get; } |
|
|
|
public void Dispose() |
|
{ |
|
IsDisposed = true; |
|
CurrentCts.Cancel(); |
|
_targetBitmap?.Dispose(); |
|
} |
|
|
|
public bool IsDisposed { get; private set; } |
|
|
|
public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed) |
|
{ |
|
if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value) |
|
{ |
|
return null; |
|
} |
|
|
|
if (CurrentCts.IsCancellationRequested || _targetBitmap is null) |
|
{ |
|
return null; |
|
} |
|
|
|
var elapsedTicks = stopwatchElapsed.Ticks; |
|
var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks); |
|
var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x); |
|
var currentFrame = _frameTimes.IndexOf(targetFrame); |
|
if (currentFrame == -1) |
|
currentFrame = 0; |
|
|
|
if (_currentFrameIndex == currentFrame) |
|
return _targetBitmap; |
|
|
|
_iterationCount = (uint)(elapsedTicks / _totalTime.Ticks); |
|
|
|
return ProcessFrameIndex(currentFrame); |
|
} |
|
|
|
internal WriteableBitmap ProcessFrameIndex(int frameIndex) |
|
{ |
|
_gifDecoder.RenderFrame(frameIndex, _targetBitmap); |
|
_currentFrameIndex = frameIndex; |
|
|
|
return _targetBitmap; |
|
} |
|
} |
|
}
|
|
|