From fb7cca855ea0f29a1276f70269b7a8c18eb9d67a Mon Sep 17 00:00:00 2001 From: leiurayer <1432593898@qq.com> Date: Thu, 7 Sep 2023 01:14:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0gif=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Avalonia.Gif/Avalonia.Gif.csproj | 13 + src/Avalonia.Gif/BgWorkerCommand.cs | 10 + src/Avalonia.Gif/BgWorkerState.cs | 12 + src/Avalonia.Gif/Decoding/BlockTypes.cs | 10 + src/Avalonia.Gif/Decoding/ExtensionType.cs | 8 + src/Avalonia.Gif/Decoding/FrameDisposal.cs | 10 + src/Avalonia.Gif/Decoding/GifColor.cs | 36 + src/Avalonia.Gif/Decoding/GifDecoder.cs | 657 ++++++++++++++++++ src/Avalonia.Gif/Decoding/GifFrame.cs | 17 + src/Avalonia.Gif/Decoding/GifHeader.cs | 19 + src/Avalonia.Gif/Decoding/GifRect.cs | 45 ++ .../Decoding/GifRepeatBehavior.cs | 8 + .../Decoding/InvalidGifStreamException.cs | 28 + .../Decoding/LzwDecompressionException.cs | 28 + .../Extensions/StreamExtensions.cs | 82 +++ src/Avalonia.Gif/GifImage.cs | 276 ++++++++ src/Avalonia.Gif/GifInstance.cs | 140 ++++ src/Avalonia.Gif/InvalidGifStreamException.cs | 25 + src/Downkyi.sln | 10 +- third_party/Avalonia.GIF-11rc1.zip | Bin 0 -> 8964111 bytes 20 files changed, 1432 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Gif/Avalonia.Gif.csproj create mode 100644 src/Avalonia.Gif/BgWorkerCommand.cs create mode 100644 src/Avalonia.Gif/BgWorkerState.cs create mode 100644 src/Avalonia.Gif/Decoding/BlockTypes.cs create mode 100644 src/Avalonia.Gif/Decoding/ExtensionType.cs create mode 100644 src/Avalonia.Gif/Decoding/FrameDisposal.cs create mode 100644 src/Avalonia.Gif/Decoding/GifColor.cs create mode 100644 src/Avalonia.Gif/Decoding/GifDecoder.cs create mode 100644 src/Avalonia.Gif/Decoding/GifFrame.cs create mode 100644 src/Avalonia.Gif/Decoding/GifHeader.cs create mode 100644 src/Avalonia.Gif/Decoding/GifRect.cs create mode 100644 src/Avalonia.Gif/Decoding/GifRepeatBehavior.cs create mode 100644 src/Avalonia.Gif/Decoding/InvalidGifStreamException.cs create mode 100644 src/Avalonia.Gif/Decoding/LzwDecompressionException.cs create mode 100644 src/Avalonia.Gif/Extensions/StreamExtensions.cs create mode 100644 src/Avalonia.Gif/GifImage.cs create mode 100644 src/Avalonia.Gif/GifInstance.cs create mode 100644 src/Avalonia.Gif/InvalidGifStreamException.cs create mode 100644 third_party/Avalonia.GIF-11rc1.zip diff --git a/src/Avalonia.Gif/Avalonia.Gif.csproj b/src/Avalonia.Gif/Avalonia.Gif.csproj new file mode 100644 index 0000000..35b5845 --- /dev/null +++ b/src/Avalonia.Gif/Avalonia.Gif.csproj @@ -0,0 +1,13 @@ + + + net6.0 + latest + true + enable + + + + + + + diff --git a/src/Avalonia.Gif/BgWorkerCommand.cs b/src/Avalonia.Gif/BgWorkerCommand.cs new file mode 100644 index 0000000..d561685 --- /dev/null +++ b/src/Avalonia.Gif/BgWorkerCommand.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Gif +{ + internal enum BgWorkerCommand + { + Null, + Play, + Pause, + Dispose + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/BgWorkerState.cs b/src/Avalonia.Gif/BgWorkerState.cs new file mode 100644 index 0000000..ab3bc38 --- /dev/null +++ b/src/Avalonia.Gif/BgWorkerState.cs @@ -0,0 +1,12 @@ +namespace Avalonia.Gif +{ + internal enum BgWorkerState + { + Null, + Start, + Running, + Paused, + Complete, + Dispose + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/BlockTypes.cs b/src/Avalonia.Gif/Decoding/BlockTypes.cs new file mode 100644 index 0000000..ca2cc7a --- /dev/null +++ b/src/Avalonia.Gif/Decoding/BlockTypes.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Gif.Decoding +{ + internal enum BlockTypes + { + Empty = 0, + Extension = 0x21, + ImageDescriptor = 0x2C, + Trailer = 0x3B, + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/ExtensionType.cs b/src/Avalonia.Gif/Decoding/ExtensionType.cs new file mode 100644 index 0000000..ad7878b --- /dev/null +++ b/src/Avalonia.Gif/Decoding/ExtensionType.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Gif.Decoding +{ + internal enum ExtensionType + { + GraphicsControl = 0xF9, + Application = 0xFF + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/FrameDisposal.cs b/src/Avalonia.Gif/Decoding/FrameDisposal.cs new file mode 100644 index 0000000..178a3c5 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/FrameDisposal.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Gif.Decoding +{ + public enum FrameDisposal + { + Unknown = 0, + Leave = 1, + Background = 2, + Restore = 3 + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifColor.cs b/src/Avalonia.Gif/Decoding/GifColor.cs new file mode 100644 index 0000000..7534cc1 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifColor.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace Avalonia.Gif +{ + [StructLayout(LayoutKind.Explicit)] + public readonly struct GifColor + { + [FieldOffset(3)] + public readonly byte A; + + [FieldOffset(2)] + public readonly byte R; + + [FieldOffset(1)] + public readonly byte G; + + [FieldOffset(0)] + public readonly byte B; + + /// + /// A struct that represents a ARGB color and is aligned as + /// a BGRA bytefield in memory. + /// + /// Red + /// Green + /// Blue + /// Alpha + public GifColor(byte r, byte g, byte b, byte a = byte.MaxValue) + { + A = a; + R = r; + G = g; + B = b; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifDecoder.cs b/src/Avalonia.Gif/Decoding/GifDecoder.cs new file mode 100644 index 0000000..4a31742 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifDecoder.cs @@ -0,0 +1,657 @@ +// This source file's Lempel-Ziv-Welch algorithm is derived from Chromium's Android GifPlayer +// as seen here (https://github.com/chromium/chromium/blob/master/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer) +// Licensed under the Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) +// Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved. + +// The rest of the source file is licensed under MIT License. +// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. + +using Avalonia.Media.Imaging; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using static Avalonia.Gif.Extensions.StreamExtensions; + +namespace Avalonia.Gif.Decoding +{ + public sealed class GifDecoder : IDisposable + { + private static readonly ReadOnlyMemory G87AMagic + = "GIF87a"u8.ToArray().AsMemory(); + + private static readonly ReadOnlyMemory G89AMagic + = "GIF89a"u8.ToArray().AsMemory(); + + private static readonly ReadOnlyMemory NetscapeMagic + = "NETSCAPE2.0"u8.ToArray().AsMemory(); + + private static readonly TimeSpan FrameDelayThreshold = TimeSpan.FromMilliseconds(10); + private static readonly TimeSpan FrameDelayDefault = TimeSpan.FromMilliseconds(100); + private static readonly GifColor TransparentColor = new(0, 0, 0, 0); + private static readonly int MaxTempBuf = 768; + private static readonly int MaxStackSize = 4096; + private static readonly int MaxBits = 4097; + + private readonly Stream _fileStream; + private readonly CancellationToken _currentCtsToken; + private readonly bool _hasFrameBackups; + + private int _gctSize, _bgIndex, _prevFrame = -1, _backupFrame = -1; + private bool _gctUsed; + + private GifRect _gifDimensions; + + // private ulong _globalColorTable; + private readonly int _backBufferBytes; + private GifColor[] _bitmapBackBuffer; + + private short[] _prefixBuf; + private byte[] _suffixBuf; + private byte[] _pixelStack; + private byte[] _indexBuf; + private byte[] _backupFrameIndexBuf; + private volatile bool _hasNewFrame; + + public GifHeader Header { get; private set; } + + public readonly List Frames = new(); + + public PixelSize Size => new PixelSize(Header.Dimensions.Width, Header.Dimensions.Height); + + public GifDecoder(Stream fileStream, CancellationToken currentCtsToken) + { + _fileStream = fileStream; + _currentCtsToken = currentCtsToken; + + ProcessHeaderData(); + ProcessFrameData(); + + Header.IterationCount = Header.Iterations switch + { + -1 => new GifRepeatBehavior { Count = 1 }, + 0 => new GifRepeatBehavior { LoopForever = true }, + > 0 => new GifRepeatBehavior { Count = Header.Iterations }, + _ => Header.IterationCount + }; + + var pixelCount = _gifDimensions.TotalPixels; + + _hasFrameBackups = Frames + .Any(f => f.FrameDisposalMethod == FrameDisposal.Restore); + + _bitmapBackBuffer = new GifColor[pixelCount]; + _indexBuf = new byte[pixelCount]; + + if (_hasFrameBackups) + _backupFrameIndexBuf = new byte[pixelCount]; + + _prefixBuf = new short[MaxStackSize]; + _suffixBuf = new byte[MaxStackSize]; + _pixelStack = new byte[MaxStackSize + 1]; + + _backBufferBytes = pixelCount * Marshal.SizeOf(typeof(GifColor)); + } + + public void Dispose() + { + Frames.Clear(); + + _bitmapBackBuffer = null; + _prefixBuf = null; + _suffixBuf = null; + _pixelStack = null; + _indexBuf = null; + _backupFrameIndexBuf = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int PixCoord(int x, int y) => x + y * _gifDimensions.Width; + + static readonly (int Start, int Step)[] Pass = + { + (0, 8), + (4, 8), + (2, 4), + (1, 2) + }; + + private void ClearImage() + { + Array.Fill(_bitmapBackBuffer, TransparentColor); + //ClearArea(_gifDimensions); + + _prevFrame = -1; + _backupFrame = -1; + } + + public void RenderFrame(int fIndex, WriteableBitmap writeableBitmap, bool forceClear = false) + { + if (_currentCtsToken.IsCancellationRequested) + return; + + if (fIndex < 0 | fIndex >= Frames.Count) + return; + + if (_prevFrame == fIndex) + return; + + if (fIndex == 0 || forceClear || fIndex < _prevFrame) + ClearImage(); + + DisposePreviousFrame(); + + _prevFrame++; + + // render intermediate frame + for (int idx = _prevFrame; idx < fIndex; ++idx) + { + var prevFrame = Frames[idx]; + + if (prevFrame.FrameDisposalMethod == FrameDisposal.Restore) + continue; + + if (prevFrame.FrameDisposalMethod == FrameDisposal.Background) + { + ClearArea(prevFrame.Dimensions); + continue; + } + + RenderFrameAt(idx, writeableBitmap); + } + + RenderFrameAt(fIndex, writeableBitmap); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderFrameAt(int idx, WriteableBitmap writeableBitmap) + { + var tmpB = ArrayPool.Shared.Rent(MaxTempBuf); + + var curFrame = Frames[idx]; + DecompressFrameToIndexBuffer(curFrame, _indexBuf, tmpB); + + if (_hasFrameBackups & curFrame.ShouldBackup) + { + Buffer.BlockCopy(_indexBuf, 0, _backupFrameIndexBuf, 0, curFrame.Dimensions.TotalPixels); + _backupFrame = idx; + } + + DrawFrame(curFrame, _indexBuf); + + _prevFrame = idx; + _hasNewFrame = true; + + using var lockedBitmap = writeableBitmap.Lock(); + WriteBackBufToFb(lockedBitmap.Address); + + ArrayPool.Shared.Return(tmpB); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DrawFrame(GifFrame curFrame, Memory frameIndexSpan) + { + var activeColorTable = + curFrame.IsLocalColorTableUsed ? curFrame.LocalColorTable : Header.GlobarColorTable; + + var cX = curFrame.Dimensions.X; + var cY = curFrame.Dimensions.Y; + var cH = curFrame.Dimensions.Height; + var cW = curFrame.Dimensions.Width; + var tC = curFrame.TransparentColorIndex; + var hT = curFrame.HasTransparency; + + if (curFrame.IsInterlaced) + { + for (var i = 0; i < 4; i++) + { + var curPass = Pass[i]; + var y = curPass.Start; + while (y < cH) + { + DrawRow(y); + y += curPass.Step; + } + } + } + else + { + for (var i = 0; i < cH; i++) + DrawRow(i); + } + + //for (var row = 0; row < cH; row++) + void DrawRow(int row) + { + // Get the starting point of the current row on frame's index stream. + var indexOffset = row * cW; + + // Get the target backbuffer offset from the frames coords. + var targetOffset = PixCoord(cX, row + cY); + var len = _bitmapBackBuffer.Length; + + for (var i = 0; i < cW; i++) + { + var indexColor = frameIndexSpan.Span[indexOffset + i]; + + if (activeColorTable == null || targetOffset >= len || + indexColor > activeColorTable.Length) return; + + if (!(hT & indexColor == tC)) + _bitmapBackBuffer[targetOffset] = activeColorTable[indexColor]; + + targetOffset++; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DisposePreviousFrame() + { + if (_prevFrame == -1) + return; + + var prevFrame = Frames[_prevFrame]; + + switch (prevFrame.FrameDisposalMethod) + { + case FrameDisposal.Background: + ClearArea(prevFrame.Dimensions); + break; + case FrameDisposal.Restore: + if (_hasFrameBackups && _backupFrame != -1) + DrawFrame(Frames[_backupFrame], _backupFrameIndexBuf); + else + ClearArea(prevFrame.Dimensions); + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearArea(GifRect area) + { + for (var y = 0; y < area.Height; y++) + { + var targetOffset = PixCoord(area.X, y + area.Y); + for (var x = 0; x < area.Width; x++) + _bitmapBackBuffer[targetOffset + x] = TransparentColor; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecompressFrameToIndexBuffer(GifFrame curFrame, Span indexSpan, byte[] tempBuf) + { + _fileStream.Position = curFrame.LzwStreamPosition; + var totalPixels = curFrame.Dimensions.TotalPixels; + + // Initialize GIF data stream decoder. + var dataSize = curFrame.LzwMinCodeSize; + var clear = 1 << dataSize; + var endOfInformation = clear + 1; + var available = clear + 2; + var oldCode = -1; + var codeSize = dataSize + 1; + var codeMask = (1 << codeSize) - 1; + + for (var code = 0; code < clear; code++) + { + _prefixBuf[code] = 0; + _suffixBuf[code] = (byte)code; + } + + // Decode GIF pixel stream. + int bits, first, top, pixelIndex; + var datum = bits = first = top = pixelIndex = 0; + + while (pixelIndex < totalPixels) + { + var blockSize = _fileStream.ReadBlock(tempBuf); + + if (blockSize == 0) + break; + + var blockPos = 0; + + while (blockPos < blockSize) + { + datum += tempBuf[blockPos] << bits; + blockPos++; + + bits += 8; + + while (bits >= codeSize) + { + // Get the next code. + var code = datum & codeMask; + datum >>= codeSize; + bits -= codeSize; + + // Interpret the code + if (code == clear) + { + // Reset decoder. + codeSize = dataSize + 1; + codeMask = (1 << codeSize) - 1; + available = clear + 2; + oldCode = -1; + continue; + } + + // Check for explicit end-of-stream + if (code == endOfInformation) + return; + + if (oldCode == -1) + { + indexSpan[pixelIndex++] = _suffixBuf[code]; + oldCode = code; + first = code; + continue; + } + + var inCode = code; + if (code >= available) + { + _pixelStack[top++] = (byte)first; + code = oldCode; + + if (top == MaxBits) + ThrowException(); + } + + while (code >= clear) + { + if (code >= MaxBits || code == _prefixBuf[code]) + ThrowException(); + + _pixelStack[top++] = _suffixBuf[code]; + code = _prefixBuf[code]; + + if (top == MaxBits) + ThrowException(); + } + + first = _suffixBuf[code]; + _pixelStack[top++] = (byte)first; + + // Add new code to the dictionary + if (available < MaxStackSize) + { + _prefixBuf[available] = (short)oldCode; + _suffixBuf[available] = (byte)first; + available++; + + if ((available & codeMask) == 0 && available < MaxStackSize) + { + codeSize++; + codeMask += available; + } + } + + oldCode = inCode; + + // Drain the pixel stack. + do + { + indexSpan[pixelIndex++] = _pixelStack[--top]; + } while (top > 0); + } + } + } + + while (pixelIndex < totalPixels) + indexSpan[pixelIndex++] = 0; // clear missing pixels + + void ThrowException() => throw new LzwDecompressionException(); + } + + /// + /// Directly copies the struct array to a bitmap IntPtr. + /// + private void WriteBackBufToFb(IntPtr targetPointer) + { + if (_currentCtsToken.IsCancellationRequested) + return; + + if (!(_hasNewFrame & _bitmapBackBuffer != null)) return; + + unsafe + { + fixed (void* src = &_bitmapBackBuffer[0]) + Buffer.MemoryCopy(src, targetPointer.ToPointer(), (uint)_backBufferBytes, + (uint)_backBufferBytes); + _hasNewFrame = false; + } + } + + /// + /// Processes GIF Header. + /// + private void ProcessHeaderData() + { + var str = _fileStream; + var tmpB = ArrayPool.Shared.Rent(MaxTempBuf); + var tempBuf = tmpB.AsSpan(); + + var _ = str.Read(tmpB, 0, 6); + + if (!tempBuf[..3].SequenceEqual(G87AMagic[..3].Span)) + throw new InvalidGifStreamException("Not a GIF stream."); + + if (!(tempBuf[..6].SequenceEqual(G87AMagic.Span) | + tempBuf[..6].SequenceEqual(G89AMagic.Span))) + throw new InvalidGifStreamException("Unsupported GIF Version: " + + Encoding.ASCII.GetString(tempBuf[..6].ToArray())); + + ProcessScreenDescriptor(tmpB); + + Header = new GifHeader + { + Dimensions = _gifDimensions, + HasGlobalColorTable = _gctUsed, + // GlobalColorTableCacheID = _globalColorTable, + GlobarColorTable = ProcessColorTable(ref str, tmpB, _gctSize), + GlobalColorTableSize = _gctSize, + BackgroundColorIndex = _bgIndex, + HeaderSize = _fileStream.Position + }; + + ArrayPool.Shared.Return(tmpB); + } + + /// + /// Parses colors from file stream to target color table. + /// + private static GifColor[] ProcessColorTable(ref Stream stream, byte[] rawBufSpan, int nColors) + { + var nBytes = 3 * nColors; + var target = new GifColor[nColors]; + + var n = stream.Read(rawBufSpan, 0, nBytes); + + if (n < nBytes) + throw new InvalidOperationException("Wrong color table bytes."); + + int i = 0, j = 0; + + while (i < nColors) + { + var r = rawBufSpan[j++]; + var g = rawBufSpan[j++]; + var b = rawBufSpan[j++]; + target[i++] = new GifColor(r, g, b); + } + + return target; + } + + /// + /// Parses screen and other GIF descriptors. + /// + private void ProcessScreenDescriptor(byte[] tempBuf) + { + var width = _fileStream.ReadUShortS(tempBuf); + var height = _fileStream.ReadUShortS(tempBuf); + + var packed = _fileStream.ReadByteS(tempBuf); + + _gctUsed = (packed & 0x80) != 0; + _gctSize = 2 << (packed & 7); + _bgIndex = _fileStream.ReadByteS(tempBuf); + + _gifDimensions = new GifRect(0, 0, width, height); + _fileStream.Skip(1); + } + + /// + /// Parses all frame data. + /// + private void ProcessFrameData() + { + _fileStream.Position = Header.HeaderSize; + + var tempBuf = ArrayPool.Shared.Rent(MaxTempBuf); + + var terminate = false; + var curFrame = 0; + + Frames.Add(new GifFrame()); + + do + { + var blockType = (BlockTypes)_fileStream.ReadByteS(tempBuf); + + switch (blockType) + { + case BlockTypes.Empty: + break; + + case BlockTypes.Extension: + ProcessExtensions(ref curFrame, tempBuf); + break; + + case BlockTypes.ImageDescriptor: + ProcessImageDescriptor(ref curFrame, tempBuf); + _fileStream.SkipBlocks(tempBuf); + break; + + case BlockTypes.Trailer: + Frames.RemoveAt(Frames.Count - 1); + terminate = true; + break; + + default: + _fileStream.SkipBlocks(tempBuf); + break; + } + + // Break the loop when the stream is not valid anymore. + if (_fileStream.Position >= _fileStream.Length & terminate == false) + throw new InvalidProgramException("Reach the end of the filestream without trailer block."); + } while (!terminate); + + ArrayPool.Shared.Return(tempBuf); + } + + /// + /// Parses GIF Image Descriptor Block. + /// + private void ProcessImageDescriptor(ref int curFrame, byte[] tempBuf) + { + var str = _fileStream; + var currentFrame = Frames[curFrame]; + + // Parse frame dimensions. + var frameX = str.ReadUShortS(tempBuf); + var frameY = str.ReadUShortS(tempBuf); + var frameW = str.ReadUShortS(tempBuf); + var frameH = str.ReadUShortS(tempBuf); + + frameW = (ushort)Math.Min(frameW, _gifDimensions.Width - frameX); + frameH = (ushort)Math.Min(frameH, _gifDimensions.Height - frameY); + + currentFrame.Dimensions = new GifRect(frameX, frameY, frameW, frameH); + + // Unpack interlace and lct info. + var packed = str.ReadByteS(tempBuf); + currentFrame.IsInterlaced = (packed & 0x40) != 0; + currentFrame.IsLocalColorTableUsed = (packed & 0x80) != 0; + currentFrame.LocalColorTableSize = (int)Math.Pow(2, (packed & 0x07) + 1); + + if (currentFrame.IsLocalColorTableUsed) + currentFrame.LocalColorTable = + ProcessColorTable(ref str, tempBuf, currentFrame.LocalColorTableSize); + + currentFrame.LzwMinCodeSize = str.ReadByteS(tempBuf); + currentFrame.LzwStreamPosition = str.Position; + + curFrame += 1; + Frames.Add(new GifFrame()); + } + + /// + /// Parses GIF Extension Blocks. + /// + private void ProcessExtensions(ref int curFrame, byte[] tempBuf) + { + var extType = (ExtensionType)_fileStream.ReadByteS(tempBuf); + + switch (extType) + { + case ExtensionType.GraphicsControl: + + _fileStream.ReadBlock(tempBuf); + var currentFrame = Frames[curFrame]; + var packed = tempBuf[0]; + + currentFrame.FrameDisposalMethod = (FrameDisposal)((packed & 0x1c) >> 2); + + if (currentFrame.FrameDisposalMethod != FrameDisposal.Restore + && currentFrame.FrameDisposalMethod != FrameDisposal.Background) + currentFrame.ShouldBackup = true; + + currentFrame.HasTransparency = (packed & 1) != 0; + + currentFrame.FrameDelay = + TimeSpan.FromMilliseconds(SpanToShort(tempBuf.AsSpan(1)) * 10); + + if (currentFrame.FrameDelay <= FrameDelayThreshold) + currentFrame.FrameDelay = FrameDelayDefault; + + currentFrame.TransparentColorIndex = tempBuf[3]; + break; + + case ExtensionType.Application: + var blockLen = _fileStream.ReadBlock(tempBuf); + var _ = tempBuf.AsSpan(0, blockLen); + var blockHeader = tempBuf.AsSpan(0, NetscapeMagic.Length); + + if (blockHeader.SequenceEqual(NetscapeMagic.Span)) + { + var count = 1; + + while (count > 0) + count = _fileStream.ReadBlock(tempBuf); + + var iterationCount = SpanToShort(tempBuf.AsSpan(1)); + + Header.Iterations = iterationCount; + } + else + _fileStream.SkipBlocks(tempBuf); + + break; + + default: + _fileStream.SkipBlocks(tempBuf); + break; + } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifFrame.cs b/src/Avalonia.Gif/Decoding/GifFrame.cs new file mode 100644 index 0000000..e15a201 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifFrame.cs @@ -0,0 +1,17 @@ +using System; + +namespace Avalonia.Gif.Decoding +{ + public class GifFrame + { + public bool HasTransparency, IsInterlaced, IsLocalColorTableUsed; + public byte TransparentColorIndex; + public int LzwMinCodeSize, LocalColorTableSize; + public long LzwStreamPosition; + public TimeSpan FrameDelay; + public FrameDisposal FrameDisposalMethod; + public bool ShouldBackup; + public GifRect Dimensions; + public GifColor[] LocalColorTable; + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifHeader.cs b/src/Avalonia.Gif/Decoding/GifHeader.cs new file mode 100644 index 0000000..d02dbc9 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifHeader.cs @@ -0,0 +1,19 @@ +// Licensed under the MIT License. +// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. + +namespace Avalonia.Gif.Decoding +{ + public class GifHeader + { + public bool HasGlobalColorTable; + public int GlobalColorTableSize; + public ulong GlobalColorTableCacheId; + public int BackgroundColorIndex; + public long HeaderSize; + internal int Iterations = -1; + public GifRepeatBehavior IterationCount; + public GifRect Dimensions; + private GifColor[] _globarColorTable; + public GifColor[] GlobarColorTable; + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifRect.cs b/src/Avalonia.Gif/Decoding/GifRect.cs new file mode 100644 index 0000000..0ed9308 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifRect.cs @@ -0,0 +1,45 @@ +namespace Avalonia.Gif.Decoding +{ + public readonly struct GifRect + { + public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } + public int TotalPixels { get; } + + public GifRect(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + TotalPixels = width * height; + } + + public static bool operator ==(GifRect a, GifRect b) + { + return a.X == b.X && + a.Y == b.Y && + a.Width == b.Width && + a.Height == b.Height; + } + public static bool operator !=(GifRect a, GifRect b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + return false; + + return this == (GifRect)obj; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() | Width.GetHashCode() ^ Height.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/GifRepeatBehavior.cs b/src/Avalonia.Gif/Decoding/GifRepeatBehavior.cs new file mode 100644 index 0000000..4b27a7b --- /dev/null +++ b/src/Avalonia.Gif/Decoding/GifRepeatBehavior.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Gif.Decoding +{ + public class GifRepeatBehavior + { + public bool LoopForever { get; set; } + public int? Count { get; set; } + } +} diff --git a/src/Avalonia.Gif/Decoding/InvalidGifStreamException.cs b/src/Avalonia.Gif/Decoding/InvalidGifStreamException.cs new file mode 100644 index 0000000..73c3124 --- /dev/null +++ b/src/Avalonia.Gif/Decoding/InvalidGifStreamException.cs @@ -0,0 +1,28 @@ +// Licensed under the MIT License. +// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. + +using System; +using System.Runtime.Serialization; + +namespace Avalonia.Gif.Decoding +{ + [Serializable] + public class InvalidGifStreamException : Exception + { + public InvalidGifStreamException() + { + } + + public InvalidGifStreamException(string message) : base(message) + { + } + + public InvalidGifStreamException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidGifStreamException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Decoding/LzwDecompressionException.cs b/src/Avalonia.Gif/Decoding/LzwDecompressionException.cs new file mode 100644 index 0000000..2e74aaf --- /dev/null +++ b/src/Avalonia.Gif/Decoding/LzwDecompressionException.cs @@ -0,0 +1,28 @@ +// Licensed under the MIT License. +// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. + +using System; +using System.Runtime.Serialization; + +namespace Avalonia.Gif.Decoding +{ + [Serializable] + public class LzwDecompressionException : Exception + { + public LzwDecompressionException() + { + } + + public LzwDecompressionException(string message) : base(message) + { + } + + public LzwDecompressionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected LzwDecompressionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/Extensions/StreamExtensions.cs b/src/Avalonia.Gif/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..d05bc63 --- /dev/null +++ b/src/Avalonia.Gif/Extensions/StreamExtensions.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Avalonia.Gif.Extensions +{ + [DebuggerStepThrough] + internal static class StreamExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort SpanToShort(Span b) => (ushort)(b[0] | (b[1] << 8)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Skip(this Stream stream, long count) + { + stream.Position += count; + } + + /// + /// Read a Gif block from stream while advancing the position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadBlock(this Stream stream, byte[] tempBuf) + { + stream.Read(tempBuf, 0, 1); + + var blockLength = (int)tempBuf[0]; + + if (blockLength > 0) + stream.Read(tempBuf, 0, blockLength); + + // Guard against infinite loop. + if (stream.Position >= stream.Length) + throw new InvalidGifStreamException("Reach the end of the filestream without trailer block."); + + return blockLength; + } + + /// + /// Skips GIF blocks until it encounters an empty block. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipBlocks(this Stream stream, byte[] tempBuf) + { + int blockLength; + do + { + stream.Read(tempBuf, 0, 1); + + blockLength = tempBuf[0]; + stream.Position += blockLength; + + // Guard against infinite loop. + if (stream.Position >= stream.Length) + throw new InvalidGifStreamException("Reach the end of the filestream without trailer block."); + + } while (blockLength > 0); + } + + /// + /// Read a from stream by providing a temporary buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUShortS(this Stream stream, byte[] tempBuf) + { + stream.Read(tempBuf, 0, 2); + return SpanToShort(tempBuf); + } + + /// + /// Read a from stream by providing a temporary buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReadByteS(this Stream stream, byte[] tempBuf) + { + stream.Read(tempBuf, 0, 1); + var finalVal = tempBuf[0]; + return finalVal; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/GifImage.cs b/src/Avalonia.Gif/GifImage.cs new file mode 100644 index 0000000..26c96a9 --- /dev/null +++ b/src/Avalonia.Gif/GifImage.cs @@ -0,0 +1,276 @@ +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Logging; +using Avalonia.Media; +using Avalonia.Rendering.Composition; +using Avalonia.VisualTree; +using System; +using System.IO; +using System.Numerics; + +namespace Avalonia.Gif +{ + public class GifImage : Control + { + public static readonly StyledProperty SourceUriRawProperty = + AvaloniaProperty.Register("SourceUriRaw"); + + public static readonly StyledProperty SourceUriProperty = + AvaloniaProperty.Register("SourceUri"); + + public static readonly StyledProperty SourceStreamProperty = + AvaloniaProperty.Register("SourceStream"); + + public static readonly StyledProperty IterationCountProperty = + AvaloniaProperty.Register("IterationCount", IterationCount.Infinite); + + private GifInstance? _gifInstance; + + public static readonly StyledProperty StretchDirectionProperty = + AvaloniaProperty.Register("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 GifInstance? _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 GifInstance 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 GifInstance(source); + _gifInstance.IterationCount = IterationCount; + _customVisual?.SendHandlerMessage(_gifInstance); + } + } +} diff --git a/src/Avalonia.Gif/GifInstance.cs b/src/Avalonia.Gif/GifInstance.cs new file mode 100644 index 0000000..c582453 --- /dev/null +++ b/src/Avalonia.Gif/GifInstance.cs @@ -0,0 +1,140 @@ +using Avalonia.Animation; +using Avalonia.Gif.Decoding; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace Avalonia.Gif +{ + public class GifInstance : IDisposable + { + 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 _frameTimes; + private uint _iterationCount; + private int _currentFrameIndex; + private readonly List _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")) + throw new InvalidDataException( + "The URI provided is not currently supported."); + + 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; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Gif/InvalidGifStreamException.cs b/src/Avalonia.Gif/InvalidGifStreamException.cs new file mode 100644 index 0000000..dc10557 --- /dev/null +++ b/src/Avalonia.Gif/InvalidGifStreamException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace Avalonia.Gif +{ + [Serializable] + internal class InvalidGifStreamException : Exception + { + public InvalidGifStreamException() + { + } + + public InvalidGifStreamException(string message) : base(message) + { + } + + public InvalidGifStreamException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidGifStreamException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/Downkyi.sln b/src/Downkyi.sln index 8685ada..de1498f 100644 --- a/src/Downkyi.sln +++ b/src/Downkyi.sln @@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Downkyi.UI", "Downkyi.UI\Do EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Downkyi.Core", "Downkyi.Core\Downkyi.Core.csproj", "{5CED42DB-2155-45D0-B195-57BB894CB97B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BiliSharp", "BiliSharp\BiliSharp.csproj", "{199B3491-51F0-460D-AB90-191B7DD377D6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BiliSharp", "BiliSharp\BiliSharp.csproj", "{199B3491-51F0-460D-AB90-191B7DD377D6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BiliSharp.UnitTest", "BiliSharp.UnitTest\BiliSharp.UnitTest.csproj", "{107D1B61-6936-45A0-B4AF-3776C736649A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BiliSharp.UnitTest", "BiliSharp.UnitTest\BiliSharp.UnitTest.csproj", "{107D1B61-6936-45A0-B4AF-3776C736649A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gif", "Avalonia.Gif\Avalonia.Gif.csproj", "{0E32DD42-C1C8-45A4-9572-8D963D85B0EF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {107D1B61-6936-45A0-B4AF-3776C736649A}.Debug|Any CPU.Build.0 = Debug|Any CPU {107D1B61-6936-45A0-B4AF-3776C736649A}.Release|Any CPU.ActiveCfg = Release|Any CPU {107D1B61-6936-45A0-B4AF-3776C736649A}.Release|Any CPU.Build.0 = Release|Any CPU + {0E32DD42-C1C8-45A4-9572-8D963D85B0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E32DD42-C1C8-45A4-9572-8D963D85B0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E32DD42-C1C8-45A4-9572-8D963D85B0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E32DD42-C1C8-45A4-9572-8D963D85B0EF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/third_party/Avalonia.GIF-11rc1.zip b/third_party/Avalonia.GIF-11rc1.zip new file mode 100644 index 0000000000000000000000000000000000000000..75c53c896f16c3b310f14d49553d4700c19df0d5 GIT binary patch literal 8964111 zcmb4qbBr)fljhjAZQHhI-mz`lwr$(CZQC~9v3 zlZ4wLan-duHPM;uYQdj`WRM6F2H-L=?C0eh4nV50#&r`D8zdk|IQ!Gh*}k7|R?`IJ zlgMVwip^1n64j}2VbZ?IR7nH+Gx;Q^TG}-D+A>USH+5n_aNU@B0ct3RLZjDI^P=|j z6p&vxRlKr=5^ka6b+7@8yxK^q2uqc-?3!MKEvfeMp;D*~T~QOw^~!^r!XJgxB}w%Ov!G_0erA*%gV)tc0b|ezH0!8KE&G z5PN}(tjxNGOZ7fPKX$9br1mftRZIYD8gxl)=isT2mKc(qE16b{E=`Ow4QF`b2fN3# z+sSfFG*E8u_4e23ta%?^FLv}8ahKNC^E+mTcDEl6wTPB7Ec@e(EIYMb zN*59xE*5!Nx4mHN)8!7vE(CcDmaLV^^b&W~=FH^s9cG4!S(IG{3>fB3IjCxWU8dXH zwgdHavSp?1#apO1vt4zVx?p++OkD$LQ4l~#(c;YE^X zjW8kw>0i(;3Lw&Jq1>iQr#;rXbhqq%%_Luk`lEO={%@2*A|y#|d@9)vvKt%0>4L4+ zzvQjii)LTv@Qa*|^-ar5`w@Na#|cNmRE6XRtcIIl8-wjcz1sYIENtBf10n>7W65*t zZ{8vX&eL?tfQxmL(=}i^4@Fs?W1f2xiESJlBH66jLb)+1m1m0qQ!=0G1cTU&mF)q) z3=udOI7k!lH%CoP)=3kg#sZ!9yT3NZc0*XI`y1GCj~fWqe%IBH8ZUG0rMw~N;&-Pq zoi6!sq9wnceP`F9@;Mcvej2}kzq4k8QDIxj3BluG)X!_jlyN8<1n&l)hWKFfK1m7= z{Ro)PoVa+1X|AP=gSE3Tpr%X_NfQK;;30X5X+xoWXj}E%Vi`H^9`q;Gj#ttpG>kS_ z#jH#e?=F$nhqB))$JUVGBUbRP?&k)e6148E;iJZ`C2QSI~~2-K3?Ru zYFLKB*-xsdj8~v71<@|aeu2m%$u%-|`LrIVuxb&SW?fbI<_7V;>M^gixz`#sm4>&M z-nW&$%^w4-&-XtRYX{xi|KzC88C|1T*s{zc6sXshBkDGyCD?}K@D~2--q`iw!I#sP!y>^u9rws6@ zs5vWPv}>-*~Daw-V2SvYw zgYPg`z0?7>3rk~RCGqGV6|B%E*wUnaQ?2#gx|L{8^2;RmjMw$NzHh z0SHLe1L21dSsN6K`RtM#vTRbsyL0Qh1XI*`Fp86WMDJd_M?b&{rnlo10l4IorUEhp zHAMyzXCR`4!;}|uQ>{>R9K+o6FpU8sR|%P;GpW5K(^5w$h&=fNGhqfu_ow?a;;=LC z`yYpaTMur8alp8WYa<=jE-NZfK?AC!%3TjS=EPf0o4)Yt{DaeZN-8BrR?^CQeN}HLtBPBsH~%+ zqSAY7t}DvC;(Dy*d-Rte|H<#pAX;v(YouLSB%)sG;owwnR>og#9DDvLwIA3;Z#-_c zn$rB6-_V}oX)A1{LfAw8VV>7-H_olp=7@)%uggyd%`H)A7YG1Sem)Qctn0pSnyfY( zskO%voSz3>kRd#imvnUv^$?HBWA3zmb%Y!^ybf{!JL@DhGl0(zmpFj0jPh9eG{Q*! z2}G_^cmPeAs@|2wmW)0}qm5Laa$-ooKtU$5^D#}3OVPc#J0j5Qy*xGk z*Kyc&0h8*l*e2)D0=ZvVnQ)c5*%�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 _LWlmt%Ubfv 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