diff --git a/Avalonia.Labs.Gif/Avalonia.Labs.Gif.csproj b/Avalonia.Labs.Gif/Avalonia.Labs.Gif.csproj deleted file mode 100644 index 7356e1eccf5d118c49cfdd35e1e578ddd3c26fae..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Avalonia.Labs.Gif.csproj +++ /dev/null @@ -1,15 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFrameworks>netstandard2.1;net6.0</TargetFrameworks> - <LangVersion>latest</LangVersion> - <IsPackable>true</IsPackable> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Avalonia" Version="11.2.5" /> - <PackageReference Include="Avalonia.Skia" Version="11.2.5" /> - </ItemGroup> - -</Project> diff --git a/Avalonia.Labs.Gif/Decoding/BlockTypes.cs b/Avalonia.Labs.Gif/Decoding/BlockTypes.cs deleted file mode 100644 index abfd3167393f4652d615d3933aa94156eff74afb..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/BlockTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.Labs.Gif.Decoding; - -internal enum BlockTypes -{ - Empty = 0, - Extension = 0x21, - ImageDescriptor = 0x2C, - Trailer = 0x3B, -} \ No newline at end of file diff --git a/Avalonia.Labs.Gif/Decoding/ExtensionType.cs b/Avalonia.Labs.Gif/Decoding/ExtensionType.cs deleted file mode 100644 index aa35e14af5c6b0b00f74d7dbcb644edddd19ce68..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/ExtensionType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Avalonia.Labs.Gif.Decoding; - -internal enum ExtensionType -{ - GraphicsControl = 0xF9, - Application = 0xFF -} \ No newline at end of file diff --git a/Avalonia.Labs.Gif/Decoding/FrameDisposal.cs b/Avalonia.Labs.Gif/Decoding/FrameDisposal.cs deleted file mode 100644 index 4f0607f12147dc02d28df2c6a652a830e7633818..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/FrameDisposal.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.Labs.Gif.Decoding; - -internal enum FrameDisposal -{ - Unknown = 0, - Leave = 1, - Background = 2, - Restore = 3 -} diff --git a/Avalonia.Labs.Gif/Decoding/GifColor.cs b/Avalonia.Labs.Gif/Decoding/GifColor.cs deleted file mode 100644 index 407673405296f927b88b5500a2890399b51fb6e5..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifColor.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Avalonia.Labs.Gif.Decoding; - -[StructLayout(LayoutKind.Explicit)] -internal 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; - - /// <summary> - /// A struct that represents a ARGB color and is aligned as - /// a BGRA bytefield in memory. - /// </summary> - /// <param name="r">Red</param> - /// <param name="g">Green</param> - /// <param name="b">Blue</param> - /// <param name="a">Alpha</param> - public GifColor(byte r, byte g, byte b, byte a = byte.MaxValue) - { - A = a; - R = r; - G = g; - B = b; - } -} diff --git a/Avalonia.Labs.Gif/Decoding/GifDecoder.cs b/Avalonia.Labs.Gif/Decoding/GifDecoder.cs deleted file mode 100644 index 63878bf5b7ea1e2ecc75dfb94dd41568dfe73422..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifDecoder.cs +++ /dev/null @@ -1,661 +0,0 @@ -// 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 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 Avalonia.Media.Imaging; -using static Avalonia.Labs.Gif.Extensions.StreamExtensions; - -namespace Avalonia.Labs.Gif.Decoding; - -internal sealed class GifDecoder : IDisposable -{ - private static readonly ReadOnlyMemory<byte> s_g87AMagic - = "GIF87a"u8.ToArray().AsMemory(); - - private static readonly ReadOnlyMemory<byte> s_g89AMagic - = "GIF89a"u8.ToArray().AsMemory(); - - private static readonly ReadOnlyMemory<byte> s_netscapeMagic - = "NETSCAPE2.0"u8.ToArray().AsMemory(); - - private static readonly TimeSpan s_frameDelayThreshold = TimeSpan.FromMilliseconds(10); - private static readonly TimeSpan s_frameDelayDefault = TimeSpan.FromMilliseconds(100); - private static readonly GifColor s_transparentColor = new(0, 0, 0, 0); - private const int MaxTempBuf = 768; - private const int MaxStackSize = 4096; - private const int MaxBits = 4097; - - private readonly Stream _fileStream; - private readonly CancellationToken _currentCtsToken; - private readonly bool _hasFrameBackups; - - private int _gctSize, _prevFrame = -1, _backupFrame = -1; - private bool _gctUsed; - - private GifRect _gifDimensions; - - 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; } - - internal readonly List<GifFrame> _frames = new(); - - public PixelSize Size => new(Header?.Dimensions.Width ?? 0, Header?.Dimensions.Height ?? 0); - - public GifDecoder(Stream fileStream, CancellationToken currentCtsToken) - { - _fileStream = fileStream; - _currentCtsToken = currentCtsToken; - - ProcessHeaderData(); - ProcessFrameData(); - - if (Header != null) - 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 PixelCoordinate(int x, int y) => x + y * _gifDimensions.Width; - - private static readonly (int Start, int Step)[] s_pass = [(0, 8), (4, 8), (2, 4), (1, 2)]; - - private void ClearImage() - { - if (_bitmapBackBuffer != null) - Array.Fill(_bitmapBackBuffer, s_transparentColor); - - _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) - { - if(writeableBitmap is null) - return; - - var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf); - - var curFrame = _frames[idx]; - DecompressFrameToIndexBuffer(curFrame, _indexBuf, tmpB); - - if (_hasFrameBackups & curFrame.ShouldBackup - && _indexBuf != null && _backupFrameIndexBuf != null) - { - 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<byte>.Shared.Return(tmpB); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawFrame(GifFrame curFrame, Memory<byte> frameIndexSpan) - { - var activeColorTable = - curFrame.IsLocalColorTableUsed ? curFrame.LocalColorTable : Header?.GlobalColorTable; - - 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) - { - int curSrcRow = 0; - for (var i = 0; i < 4; i++) - { - var curPass = s_pass[i]; - var y = curPass.Start; - while (y < cH) - { - DrawRow(curSrcRow++, y); - y += curPass.Step; - } - } - } - else - { - for (var i = 0; i < cH; i++) - DrawRow(i, i); - } - - return; - - void DrawRow(int srcRow, int destRow) - { - // Get the starting point of the current row on frame's index stream. - var indexOffset = srcRow * cW; - - // Get the target back buffer offset from the frames coords. - var targetOffset = PixelCoordinate(cX, destRow + cY); - if (_bitmapBackBuffer == null) return; - 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) - { - if (_bitmapBackBuffer is null) return; - - for (var y = 0; y < area.Height; y++) - { - var targetOffset = PixelCoordinate(area.X, y + area.Y); - for (var x = 0; x < area.Width; x++) - _bitmapBackBuffer[targetOffset + x] = s_transparentColor; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecompressFrameToIndexBuffer(GifFrame curFrame, Span<byte> indexSpan, byte[] tempBuf) - { - if (_prefixBuf is null || _suffixBuf is null || _pixelStack is null) return; - - _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) - ThrowLswException(); - } - - while (code >= clear) - { - if (code >= MaxBits || code == _prefixBuf[code]) - ThrowLswException(); - - _pixelStack[top++] = _suffixBuf[code]; - code = _prefixBuf[code]; - - if (top == MaxBits) - ThrowLswException(); - } - - 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 - } - - private static void ThrowLswException() => throw new LzwDecompressionException(); - - /// <summary> - /// Directly copies the <see cref="GifColor"/> struct array to a bitmap IntPtr. - /// </summary> - 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; - } - } - - /// <summary> - /// Processes GIF Header. - /// </summary> - private void ProcessHeaderData() - { - var str = _fileStream; - var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf); - var tempBuf = tmpB.AsSpan(); - - var _ = str.Read(tmpB, 0, 6); - - if (!tempBuf[..3].SequenceEqual(s_g87AMagic[..3].Span)) - throw new InvalidGifStreamException("Not a GIF stream."); - - if (!(tempBuf[..6].SequenceEqual(s_g87AMagic.Span) | - tempBuf[..6].SequenceEqual(s_g89AMagic.Span))) - throw new InvalidGifStreamException("Unsupported GIF Version: " + - Encoding.ASCII.GetString(tempBuf[..6].ToArray())); - - ProcessScreenDescriptor(tmpB); - - Header = new GifHeader - { - Dimensions = _gifDimensions, - GlobalColorTable = - _gctUsed ? ProcessColorTable(ref str, tmpB, _gctSize) : [], - HeaderSize = _fileStream.Position - }; - - ArrayPool<byte>.Shared.Return(tmpB); - } - - /// <summary> - /// Parses colors from file stream to target color table. - /// </summary> - 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; - } - - /// <summary> - /// Parses screen and other GIF descriptors. - /// </summary> - 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); - _ = _fileStream.ReadByteS(tempBuf); - - _gifDimensions = new GifRect(0, 0, width, height); - _fileStream.Skip(1); - } - - /// <summary> - /// Parses all frame data. - /// </summary> - private void ProcessFrameData() - { - _fileStream.Position = Header?.HeaderSize ?? -1; - - var tempBuf = ArrayPool<byte>.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<byte>.Shared.Return(tempBuf); - } - - /// <summary> - /// Parses GIF Image Descriptor Block. - /// </summary> - 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()); - } - - /// <summary> - /// Parses GIF Extension Blocks. - /// </summary> - 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 <= s_frameDelayThreshold) - currentFrame.FrameDelay = s_frameDelayDefault; - - currentFrame.TransparentColorIndex = tempBuf[3]; - break; - - case ExtensionType.Application: - var blockLen = _fileStream.ReadBlock(tempBuf); - var _ = tempBuf.AsSpan(0, blockLen); - var blockHeader = tempBuf.AsSpan(0, s_netscapeMagic.Length); - - if (blockHeader.SequenceEqual(s_netscapeMagic.Span)) - { - var count = 1; - - while (count > 0) - count = _fileStream.ReadBlock(tempBuf); - - var iterationCount = SpanToShort(tempBuf.AsSpan(1)); - - if (Header != null) - Header.Iterations = iterationCount; - } - else - _fileStream.SkipBlocks(tempBuf); - - break; - - default: - _fileStream.SkipBlocks(tempBuf); - break; - } - } -} diff --git a/Avalonia.Labs.Gif/Decoding/GifFrame.cs b/Avalonia.Labs.Gif/Decoding/GifFrame.cs deleted file mode 100644 index e10cbb6345f8ce1fa8eda5a9f23f338630913a89..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifFrame.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Avalonia.Labs.Gif.Decoding; - -internal 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; -} diff --git a/Avalonia.Labs.Gif/Decoding/GifHeader.cs b/Avalonia.Labs.Gif/Decoding/GifHeader.cs deleted file mode 100644 index 69ae294436da5cbd3f8d94b2dbbedde582f0880b..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifHeader.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed under the MIT License. -// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. - -namespace Avalonia.Labs.Gif.Decoding; - -internal class GifHeader -{ - public long HeaderSize; - internal int Iterations = -1; - public GifRepeatBehavior? IterationCount; - public GifRect Dimensions; - public GifColor[]? GlobalColorTable; -} diff --git a/Avalonia.Labs.Gif/Decoding/GifRect.cs b/Avalonia.Labs.Gif/Decoding/GifRect.cs deleted file mode 100644 index d4d6f817d66ffeed6acd921bd2d3f9f92d8d5448..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifRect.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Avalonia.Labs.Gif.Decoding; - -internal 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(); - } -} diff --git a/Avalonia.Labs.Gif/Decoding/GifRepeatBehavior.cs b/Avalonia.Labs.Gif/Decoding/GifRepeatBehavior.cs deleted file mode 100644 index a60799532b3c87ce3ffbbaecc37e570848075f38..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/GifRepeatBehavior.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Avalonia.Labs.Gif.Decoding; - -internal class GifRepeatBehavior -{ - public bool LoopForever { get; set; } - public int? Count { get; set; } -} diff --git a/Avalonia.Labs.Gif/Decoding/InvalidGifStreamException.cs b/Avalonia.Labs.Gif/Decoding/InvalidGifStreamException.cs deleted file mode 100644 index bc434ab971361f68b332ee575936fdc66f039b03..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/InvalidGifStreamException.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Licensed under the MIT License. -// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. - -using System; - -namespace Avalonia.Labs.Gif.Decoding; - -[Serializable] -internal sealed class InvalidGifStreamException(string message) : Exception(message); diff --git a/Avalonia.Labs.Gif/Decoding/LzwDecompressionException.cs b/Avalonia.Labs.Gif/Decoding/LzwDecompressionException.cs deleted file mode 100644 index e380e732fdd6fa34d07e74cf87d23c2ddf2f28f5..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Decoding/LzwDecompressionException.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Licensed under the MIT License. -// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved. - -using System; - -namespace Avalonia.Labs.Gif.Decoding; - -[Serializable] -internal class LzwDecompressionException : Exception { } diff --git a/Avalonia.Labs.Gif/Extensions/StreamExtensions.cs b/Avalonia.Labs.Gif/Extensions/StreamExtensions.cs deleted file mode 100644 index 1cbae59383bafa52eb5dd8d87796eca61ddf4d10..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/Extensions/StreamExtensions.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.IO; -using System.Runtime.CompilerServices; -using Avalonia.Labs.Gif.Decoding; - -namespace Avalonia.Labs.Gif.Extensions; - -internal static class StreamExtensions -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort SpanToShort(Span<byte> b) => (ushort)(b[0] | (b[1] << 8)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Skip(this Stream stream, long count) - { - stream.Position += count; - } - - /// <summary> - /// Read a Gif block from stream while advancing the position. - /// </summary> - [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; - } - - /// <summary> - /// Skips GIF blocks until it encounters an empty block. - /// </summary> - [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); - } - - /// <summary> - /// Read a <see cref="ushort"/> from stream by providing a temporary buffer. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ReadUShortS(this Stream stream, byte[] tempBuf) - { - _ = stream.Read(tempBuf, 0, 2); - return SpanToShort(tempBuf); - } - - /// <summary> - /// Read a <see cref="ushort"/> from stream by providing a temporary buffer. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte ReadByteS(this Stream stream, byte[] tempBuf) - { - _ = stream.Read(tempBuf, 0, 1); - var finalVal = tempBuf[0]; - return finalVal; - } -} diff --git a/Avalonia.Labs.Gif/GifCompositionCustomVisualHandler.cs b/Avalonia.Labs.Gif/GifCompositionCustomVisualHandler.cs deleted file mode 100644 index 3a270213abdb83e0e4acdc534e8cfdf3281576d4..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/GifCompositionCustomVisualHandler.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using Avalonia.Media; -using Avalonia.Rendering.Composition; - -namespace Avalonia.Labs.Gif; - -internal class GifCompositionCustomVisualHandler : CompositionCustomVisualHandler -{ - private bool _running; - private Stretch? _stretch; - private StretchDirection? _stretchDirection; - private Size _GifSize; - private readonly object _sync = new(); - private bool _isDisposed; - private GifInstance? _gifInstance; - - private TimeSpan _animationElapsed; - private TimeSpan _lastServerTime; - - public override void OnMessage(object message) - { - if (message is not GifDrawPayload msg) - { - return; - } - - switch (msg) - { - case - { - HandlerCommand: HandlerCommand.Start, Source: { } uri, IterationCount: { } iteration, - Stretch: { } st, StretchDirection: { } sd - }: - { - _gifInstance = new GifInstance(uri); - - _gifInstance.IterationCount = iteration; - - _lastServerTime = CompositionNow; - _GifSize = _gifInstance.GifPixelSize.ToSize(1); - _running = true; - _stretch = st; - _stretchDirection = sd; - RegisterForNextAnimationFrameUpdate(); - break; - } - case - { - HandlerCommand: HandlerCommand.Update, Stretch: { } st, IterationCount: { } iteration, - StretchDirection: { } sd - }: - { - _stretch = st; - _stretchDirection = sd; - if (_gifInstance != null) - _gifInstance.IterationCount = iteration; - RegisterForNextAnimationFrameUpdate(); - break; - } - case { HandlerCommand: HandlerCommand.Stop }: - { - _running = false; - break; - } - case { HandlerCommand: HandlerCommand.Dispose }: - { - DisposeImpl(); - break; - } - } - } - - public override void OnAnimationFrameUpdate() - { - if (!_running || _isDisposed) - return; - - Invalidate(); - RegisterForNextAnimationFrameUpdate(); - } - - public override void OnRender(ImmediateDrawingContext context) - { - lock (_sync) - { - if (_stretch is not { } st - || _stretchDirection is not { } sd - || _gifInstance is null - || _isDisposed - || !_running) - { - return; - } - - _animationElapsed += CompositionNow - _lastServerTime; - _lastServerTime = CompositionNow; - - var bounds = GetRenderBounds().Size; - var viewPort = new Rect(bounds); - - var scale = st.CalculateScaling(bounds, _GifSize, sd); - var scaledSize = _GifSize * scale; - var destRect = viewPort - .CenterRect(new Rect(scaledSize)) - .Intersect(viewPort); - - var bitmap = _gifInstance.ProcessFrameTime(_animationElapsed); - if (bitmap is not null) - { - context.DrawBitmap(bitmap, new Rect(_gifInstance.GifPixelSize.ToSize(1)), - destRect); - } - } - } - - private void DisposeImpl() - { - lock (_sync) - { - if (_isDisposed) return; - _isDisposed = true; - _gifInstance?.Dispose(); - _animationElapsed = TimeSpan.Zero; - _lastServerTime = TimeSpan.Zero; - _running = false; - } - } -} diff --git a/Avalonia.Labs.Gif/GifDrawPayload.cs b/Avalonia.Labs.Gif/GifDrawPayload.cs deleted file mode 100644 index b80149f94f99bfc201c58f83fac0c88a35aa62c5..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/GifDrawPayload.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Avalonia.Animation; -using Avalonia.Media; - -namespace Avalonia.Labs.Gif; - -internal record struct GifDrawPayload( - HandlerCommand HandlerCommand, - Uri? Source = default, - Size? GifSize = default, - Size? Size = default, - Stretch? Stretch = default, - StretchDirection? StretchDirection = default, - IterationCount? IterationCount = default); \ No newline at end of file diff --git a/Avalonia.Labs.Gif/GifImage.cs b/Avalonia.Labs.Gif/GifImage.cs deleted file mode 100644 index f060406c79ca7ed490baaccb43eb1eaffbc7985e..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/GifImage.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.IO; -using System.Numerics; -using System.Threading; -using Avalonia.Animation; -using Avalonia.Controls; -using Avalonia.Labs.Gif.Decoding; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering.Composition; - -namespace Avalonia.Labs.Gif; - -/// <summary> -/// A control that presents GIF animations. -/// </summary> -public class GifImage : Control -{ - private CompositionCustomVisual? _customVisual; - - private double _gifWidth, _gifHeight; - - /// <summary> - /// Defines the <see cref="Source"/> property. - /// </summary> - public static readonly StyledProperty<Uri> SourceProperty = - AvaloniaProperty.Register<GifImage, Uri>(nameof(Source)); - - /// <summary> - /// Defines the <see cref="IterationCount"/> property. - /// </summary> - public static readonly StyledProperty<IterationCount> IterationCountProperty = - AvaloniaProperty.Register<GifImage, IterationCount>(nameof(IterationCount), IterationCount.Infinite); - - /// <summary> - /// Defines the <see cref="StretchDirection"/> property. - /// </summary> - public static readonly StyledProperty<StretchDirection> StretchDirectionProperty = - AvaloniaProperty.Register<GifImage, StretchDirection>(nameof(StretchDirection)); - - /// <summary> - /// Defines the <see cref="Stretch"/> property. - /// </summary> - public static readonly StyledProperty<Stretch> StretchProperty = - AvaloniaProperty.Register<GifImage, Stretch>(nameof(Stretch)); - - /// <summary> - /// Gets or sets the uri pointing to the GIF image resource - /// </summary> - public Uri Source - { - get => GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - - /// <summary> - /// Gets or sets a value controlling how the image will be stretched. - /// </summary> - public Stretch Stretch - { - get => GetValue(StretchProperty); - set => SetValue(StretchProperty, value); - } - - /// <summary> - /// Gets or sets a value controlling in what direction the image will be stretched. - /// </summary> - public StretchDirection StretchDirection - { - get => GetValue(StretchDirectionProperty); - set => SetValue(StretchDirectionProperty, value); - } - - /// <summary> - /// Gets or sets the amount in which the GIF image loops. - /// </summary> - public IterationCount IterationCount - { - get => GetValue(IterationCountProperty); - set => SetValue(IterationCountProperty, value); - } - - static GifImage() - { - AffectsRender<GifImage>(SourceProperty, - StretchProperty, - StretchDirectionProperty, - WidthProperty, - HeightProperty); - - AffectsMeasure<GifImage>(SourceProperty, - StretchProperty, - StretchDirectionProperty, - WidthProperty, - HeightProperty); - } - - private Size GetGifSize() - { - return new Size(_gifWidth, _gifHeight); - } - - /// <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) - { - return Stretch.CalculateSize(availableSize, GetGifSize(), StretchDirection); - } - - /// <inheritdoc/> - protected override Size ArrangeOverride(Size finalSize) - { - var sourceSize = GetGifSize(); - var result = Stretch.CalculateSize(finalSize, sourceSize); - return result; - } - - /// <inheritdoc /> - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - InitializeGif(); - } - - /// <inheritdoc /> - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - var avProp = change.Property; - - if (avProp == SourceProperty) - { - InitializeGif(); - } - - if ((avProp == SourceProperty || - avProp == StretchProperty || - avProp == StretchDirectionProperty || - avProp == IterationCountProperty) && _customVisual is not null) - { - _customVisual.SendHandlerMessage( - new GifDrawPayload( - HandlerCommand.Update, - null, - GetGifSize(), - Bounds.Size, - Stretch, - StretchDirection, - IterationCount)); - } - } - - private void InitializeGif() - { - Stop(); - DisposeImpl(); - - var elemVisual = ElementComposition.GetElementVisual(this); - var compositor = elemVisual?.Compositor; - - if (compositor is null) - { - return; - } - - _customVisual = compositor.CreateCustomVisual(new GifCompositionCustomVisualHandler()); - - ElementComposition.SetElementChildVisual(this, _customVisual); - - LayoutUpdated += OnLayoutUpdated; - - _customVisual.Size = new Vector2((float)Bounds.Size.Width, (float)Bounds.Size.Height); - - using var stream = AssetLoader.Open(Source); - using var tempGifDecoder = new GifDecoder(stream, CancellationToken.None); - _gifHeight = tempGifDecoder.Size.Height; - _gifWidth = tempGifDecoder.Size.Width; - - _customVisual?.SendHandlerMessage( - new GifDrawPayload( - HandlerCommand.Start, - Source, - GetGifSize(), - Bounds.Size, - Stretch, - StretchDirection, - IterationCount)); - - InvalidateVisual(); - } - - /// <inheritdoc /> - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - LayoutUpdated -= OnLayoutUpdated; - - Stop(); - DisposeImpl(); - } - - - private void OnLayoutUpdated(object? sender, EventArgs e) - { - if (_customVisual == null) - { - return; - } - - _customVisual.Size = new Vector2((float)Bounds.Size.Width, (float)Bounds.Size.Height); - - _customVisual.SendHandlerMessage( - new GifDrawPayload( - HandlerCommand.Update, - null, - GetGifSize(), - Bounds.Size, - Stretch, - StretchDirection, - IterationCount)); - } - - private void Stop() - { - _customVisual?.SendHandlerMessage(new GifDrawPayload(HandlerCommand.Stop)); - } - - private void DisposeImpl() - { - _customVisual?.SendHandlerMessage(new GifDrawPayload(HandlerCommand.Dispose)); - _customVisual = null; - } -} diff --git a/Avalonia.Labs.Gif/GifInstance.cs b/Avalonia.Labs.Gif/GifInstance.cs deleted file mode 100644 index 05fed9ec99cc4cdd093c82242323d846bbe43579..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/GifInstance.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using Avalonia.Animation; -using Avalonia.Labs.Gif.Decoding; -using Avalonia.Media.Imaging; -using Avalonia.Platform; - -namespace Avalonia.Labs.Gif; - -internal class GifInstance : IDisposable -{ - public IterationCount IterationCount { get; set; } - private readonly GifDecoder _gifDecoder; - private readonly WriteableBitmap? _targetBitmap; - private TimeSpan _totalTime; - private readonly List<TimeSpan>? _frameTimes; - private uint _iterationCount; - private int _currentFrameIndex; - - private CancellationTokenSource CurrentCts { get; } - - internal GifInstance(object newValue) : this(newValue switch - { - Stream s => s, - _ => throw new InvalidDataException("Unsupported source object") - }) - { - } - - public GifInstance(Uri uri) : this(AssetLoader.Open(uri)) - { - } - - private 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); - - if (_gifDecoder.Header is null) - return; - - 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); - } - - public PixelSize GifPixelSize { get; } - private bool _isDisposed; - - public void Dispose() - { - if (_isDisposed) return; - - GC.SuppressFinalize(this); - - _isDisposed = true; - CurrentCts.Cancel(); - _targetBitmap?.Dispose(); - } - - public WriteableBitmap? ProcessFrameTime(TimeSpan elapsed) - { - if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value) - { - return null; - } - - if (CurrentCts.IsCancellationRequested) - { - return null; - } - - if (_frameTimes is null) - return null; - - var totalTicks = _totalTime.Ticks; - - if (totalTicks == 0) - { - return ProcessFrameIndex(0); - } - - - var elapsedTicks = elapsed.Ticks; - var timeModulus = TimeSpan.FromTicks(elapsedTicks % totalTicks); - 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 / totalTicks); - - return ProcessFrameIndex(currentFrame); - } - - private WriteableBitmap? ProcessFrameIndex(int frameIndex) - { - _gifDecoder.RenderFrame(frameIndex, _targetBitmap); - _currentFrameIndex = frameIndex; - - return _targetBitmap; - } -} diff --git a/Avalonia.Labs.Gif/HandlerCommand.cs b/Avalonia.Labs.Gif/HandlerCommand.cs deleted file mode 100644 index 322773016b53a2d535f45be3b1c5e2322b18366d..0000000000000000000000000000000000000000 --- a/Avalonia.Labs.Gif/HandlerCommand.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.Labs.Gif; - -internal enum HandlerCommand -{ - Start, - Stop, - Update, - Dispose -} \ No newline at end of file diff --git a/Blacklight.Desktop/Blacklight.Desktop.csproj b/Blacklight.Desktop/Blacklight.Desktop.csproj index aeb91b8cd77c01449fbd8d90286dcf83ae7330cb..c9947ee14d5fc965a9a644d91d758eb3d6f3b837 100644 --- a/Blacklight.Desktop/Blacklight.Desktop.csproj +++ b/Blacklight.Desktop/Blacklight.Desktop.csproj @@ -21,5 +21,9 @@ <ItemGroup> <ProjectReference Include="..\Blacklight\Blacklight.csproj" /> </ItemGroup> + + <PropertyGroup> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + </PropertyGroup> </Project> diff --git a/Blacklight.Desktop/Properties/AssemblyInfo.cs b/Blacklight.Desktop/Properties/AssemblyInfo.cs index ce09e159605a1619ea2e28ddbda11df85053ac26..63529263ed8bc47265881a358237418b34938be1 100644 --- a/Blacklight.Desktop/Properties/AssemblyInfo.cs +++ b/Blacklight.Desktop/Properties/AssemblyInfo.cs @@ -2,4 +2,5 @@ [assembly: AssemblyVersion("0.0.0.0")] [assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("0.0.0.0")] +[assembly: AssemblyCompany("015")] \ No newline at end of file diff --git a/Blacklight.sln b/Blacklight.sln index de94322bb1ddca89d0631b73a47a4ceaae0bccb5..84453fdf82826b334aaa455d1f5bf8f88dd90395 100644 --- a/Blacklight.sln +++ b/Blacklight.sln @@ -6,8 +6,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blacklight.Desktop", "Black EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lightquark.NET", "Lightquark.NET\Lightquark.NET.csproj", "{354D4E75-A2CE-46D0-BB81-778ADA819951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Labs.Gif", "Avalonia.Labs.Gif\Avalonia.Labs.Gif.csproj", "{354D4E75-A2CE-46D0-BB81-778ADADA9951}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,7 +24,5 @@ Global {354D4E75-A2CE-46D0-BB81-778ADA819951}.Debug|Any CPU.Build.0 = Debug|Any CPU {354D4E75-A2CE-46D0-BB81-778ADA819951}.Release|Any CPU.ActiveCfg = Release|Any CPU {354D4E75-A2CE-46D0-BB81-778ADA819951}.Release|Any CPU.Build.0 = Release|Any CPU - {354D4E75-A2CE-46D0-BB81-778ADADA9951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {354D4E75-A2CE-46D0-BB81-778ADADA9951}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection EndGlobal diff --git a/Blacklight/Blacklight.csproj b/Blacklight/Blacklight.csproj index f1de52206aaea161451c8a770e153bd810541076..a0cadd8e5e27383aa4b56174ba361c94fdf46a21 100644 --- a/Blacklight/Blacklight.csproj +++ b/Blacklight/Blacklight.csproj @@ -14,6 +14,7 @@ <ItemGroup> <PackageReference Include="Avalonia" Version="11.2.5" /> <PackageReference Include="Avalonia.HtmlRenderer" Version="11.0.0" /> + <PackageReference Include="Avalonia.Labs.Gif" Version="11.2.0" /> <PackageReference Include="Avalonia.Svg.Skia" Version="11.2.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" /> <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" /> @@ -42,7 +43,6 @@ <ItemGroup> - <ProjectReference Include="..\Avalonia.Labs.Gif\Avalonia.Labs.Gif.csproj" /> <ProjectReference Include="..\Lightquark.NET\Lightquark.NET.csproj" /> </ItemGroup> </Project>