File: Internals\ImageParser.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
 
namespace Microsoft.Maui.Controls.Internals
{
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderFormatException.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFDecoderFormatException']/Docs/*" />
	public class GIFDecoderFormatException : Exception
	{
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderFormatException.xml" path="//Member[@MemberName='.ctor'][1]/Docs/*" />
		public GIFDecoderFormatException()
		{
			;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderFormatException.xml" path="//Member[@MemberName='.ctor'][2]/Docs/*" />
		public GIFDecoderFormatException(string message) : base(message)
		{
			;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderFormatException.xml" path="//Member[@MemberName='.ctor'][3]/Docs/*" />
		public GIFDecoderFormatException(string message, Exception innerException) : base(message, innerException)
		{
			;
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFDecoderStreamReader']/Docs/*" />
	public class GIFDecoderStreamReader
	{
		Stream _stream;
		long _currentPosition;
		int _currentBlockSize;
		byte[] _blockBuffer = new byte[256];
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
		public GIFDecoderStreamReader(Stream stream)
		{
			_stream = stream;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='CurrentPosition']/Docs/*" />
		public long CurrentPosition
		{
			get
			{
#if DEBUG
				if (_stream.CanSeek)
					global::System.Diagnostics.Debug.Assert(_stream.Position == _currentPosition);
#endif
				return _currentPosition;
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='CurrentBlockBuffer']/Docs/*" />
		public byte[] CurrentBlockBuffer
		{
			get { return _blockBuffer; }
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='CurrentBlockSize']/Docs/*" />
		public int CurrentBlockSize
		{
			get { return _currentBlockSize; }
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='Read']/Docs/*" />
		public int Read()
		{
			_currentPosition++;
			return _stream.ReadByte();
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='ReadShort']/Docs/*" />
		public int ReadShort()
		{
			return Read() | (Read() << 8);
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='ReadString']/Docs/*" />
		public string ReadString(int length)
		{
			var buffer = new StringBuilder(length);
			for (int i = 0; i < length; i++)
				buffer.Append((char)_stream.ReadByte());
 
			_currentPosition += length;
			return buffer.ToString();
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='ReadAsync']/Docs/*" />
		public async Task<int> ReadAsync(byte[] buffer, int toRead)
		{
			int totalBytesRead = 0;
			if (toRead > 0)
			{
				Debug.Assert(toRead <= buffer.Length);
 
				int bytesRead = 0;
				while (totalBytesRead < toRead)
				{
					bytesRead = await _stream.ReadAsync(buffer, totalBytesRead, toRead - totalBytesRead);
					if (bytesRead == -1)
					{
						break;
					}
					totalBytesRead += bytesRead;
				}
			}
 
			_currentPosition += totalBytesRead;
			return totalBytesRead;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='ReadBlockAsync']/Docs/*" />
		public async Task<int> ReadBlockAsync()
		{
			_currentBlockSize = Read();
			int bytesRead = await ReadAsync(_blockBuffer, _currentBlockSize).ConfigureAwait(false);
 
			if (bytesRead < _currentBlockSize)
			{
				throw new GIFDecoderFormatException("Current block to small.");
			}
 
			Debug.Assert(_currentBlockSize == bytesRead);
			return bytesRead;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFDecoderStreamReader.xml" path="//Member[@MemberName='SkipBlockAsync']/Docs/*" />
		public async Task SkipBlockAsync()
		{
			_currentBlockSize = Read();
			while (_currentBlockSize > 0)
			{
				if (_stream.CanSeek)
				{
					_stream.Seek(_currentBlockSize, SeekOrigin.Current);
					_currentPosition += _currentBlockSize;
				}
				else
				{
					await ReadAsync(_blockBuffer, _currentBlockSize).ConfigureAwait(false);
				}
 
				_currentBlockSize = Read();
			}
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFColorTable.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFColorTable']/Docs/*" />
	public class GIFColorTable
	{
		int[] _colorTable = new int[256];
		byte[] _colorData = null;
		short _size = 0;
		int _transparencyIndex = -1;
		int _oldColorValue = -1;
 
		GIFColorTable(short size)
		{
			// Each color uses 3 bytes.
			_colorData = new byte[3 * size];
			_size = size;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFColorTable.xml" path="//Member[@MemberName='Data']/Docs/*" />
		public int[] Data
		{
			get { return _colorTable; }
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFColorTable.xml" path="//Member[@MemberName='SetTransparency']/Docs/*" />
		public void SetTransparency(int transparencyIndex)
		{
			Debug.Assert(transparencyIndex < _colorTable.Length);
 
			ResetTransparency();
 
			_oldColorValue = _colorTable[transparencyIndex];
			_colorTable[transparencyIndex] = 0;
			_transparencyIndex = transparencyIndex;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFColorTable.xml" path="//Member[@MemberName='ResetTransparency']/Docs/*" />
		public void ResetTransparency()
		{
			if (_transparencyIndex != -1)
			{
				_colorTable[_transparencyIndex] = _oldColorValue;
				_transparencyIndex = _oldColorValue = -1;
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFColorTable.xml" path="//Member[@MemberName='CreateColorTableAsync']/Docs/*" />
		public static async Task<GIFColorTable> CreateColorTableAsync(GIFDecoderStreamReader stream, short size)
		{
			var colorTable = new GIFColorTable(size);
			await colorTable.ParseAsync(stream).ConfigureAwait(false);
			return colorTable;
		}
 
		async Task ParseAsync(GIFDecoderStreamReader stream)
		{
			int toRead = _colorData.Length;
			int bytesRead = await stream.ReadAsync(_colorData, toRead).ConfigureAwait(false);
			if (bytesRead < toRead)
				throw new GIFDecoderFormatException("Invalid color table size.");
 
			int currentColor = 0;
			int currentColorData = 0;
			while (currentColor < _size)
			{
				int r = _colorData[currentColorData++];
				int g = _colorData[currentColorData++];
				int b = _colorData[currentColorData++];
 
				var rgb = (r << 16) | (g << 8) | b;
				_colorTable[currentColor++] = (int)(0xFF000000 | rgb);
			}
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFHeader']/Docs/*" />
	public class GIFHeader
	{
		GIFHeader()
		{
			;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='TypeIdentifier']/Docs/*" />
		public string TypeIdentifier { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='Version']/Docs/*" />
		public string Version { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='Width']/Docs/*" />
		public int Width { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='Height']/Docs/*" />
		public int Height { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='BackgroundColorIndex']/Docs/*" />
		public int BackgroundColorIndex { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='BackgroundColor']/Docs/*" />
		public int BackgroundColor { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='GlobalColorTable']/Docs/*" />
		public GIFColorTable GlobalColorTable { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='PixelAspectRatio']/Docs/*" />
		public int PixelAspectRatio { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='IsGIFHeader']/Docs/*" />
		public bool IsGIFHeader
		{
			get
			{
				return !string.IsNullOrEmpty(TypeIdentifier) && TypeIdentifier.StartsWith("GIF", StringComparison.OrdinalIgnoreCase);
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFHeader.xml" path="//Member[@MemberName='CreateHeaderAsync']/Docs/*" />
		public static async Task<GIFHeader> CreateHeaderAsync(GIFDecoderStreamReader stream, bool skipTypeIdentifier = false)
		{
			GIFHeader header = new GIFHeader();
			await header.ParseAsync(stream, skipTypeIdentifier).ConfigureAwait(false);
			if (!header.IsGIFHeader)
				header = null;
 
			return header;
		}
 
		bool UseGlobalColorTable(int flags)
		{
			return ((flags & 0x80) != 0);
		}
 
		short GlobalColorTableSize(int flags)
		{
			return (short)(2 << (flags & 7));
		}
 
		async Task ParseAsync(GIFDecoderStreamReader stream, bool skipTypeIdentifier)
		{
			if (!skipTypeIdentifier)
				TypeIdentifier = stream.ReadString(3);
			else
				TypeIdentifier = "GIF";
 
			if (IsGIFHeader)
			{
				Version = stream.ReadString(3);
				Width = stream.ReadShort();
				Height = stream.ReadShort();
 
				int flags = stream.Read();
				BackgroundColorIndex = stream.Read();
				PixelAspectRatio = stream.Read();
 
				if (UseGlobalColorTable(flags))
				{
					GlobalColorTable = await GIFColorTable.CreateColorTableAsync(stream, GlobalColorTableSize(flags)).ConfigureAwait(false);
					BackgroundColor = GlobalColorTable.Data[BackgroundColorIndex];
				}
			}
			else
			{
				throw new GIFDecoderFormatException("Unknown GIF format type identifier: " + TypeIdentifier);
			}
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFBitmap']/Docs/*" />
	public class GIFBitmap
	{
		public enum DisposeMethod
		{
			NoAction = 0,
			LeaveInPlace = 1,
			RestoreToBackground = 2,
			RestoreToPrevious = 3
		};
 
		public class Rect
		{
			public Rect(int x, int y, int width, int height)
			{
				X = x;
				Y = y;
				Width = width;
				Height = height;
			}
 
			public int X { get; }
 
			public int Y { get; }
 
			public int Width { get; }
 
			public int Height { get; }
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='Data']/Docs/*" />
		public int[] Data { get; set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='DataPosition']/Docs/*" />
		public long DataPosition { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='Bounds']/Docs/*" />
		public GIFBitmap.Rect Bounds { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='Dispose']/Docs/*" />
		public DisposeMethod Dispose { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='BackgroundColor']/Docs/*" />
		public int BackgroundColor { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='IsTransparent']/Docs/*" />
		public bool IsTransparent { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='TransparencyIndex']/Docs/*" />
		public int TransparencyIndex { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='IsInterlaced']/Docs/*" />
		public bool IsInterlaced { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='Delay']/Docs/*" />
		public int Delay { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='LoopCount']/Docs/*" />
		public int LoopCount { get; private set; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='ColorTable']/Docs/*" />
		public GIFColorTable ColorTable { get; private set; }
 
		class GIFBlockCodes
		{
			public const int ImageSeparator = 0x2C;
			public const int Extension = 0x21;
			public const int Trailer = 0x3B;
			public const int GraphicsControlExtension = 0xF9;
			public const int ApplicationExtensionBlock = 0xFF;
		}
 
		const string NetscapeApplicationExtensionID = "NETSCAPE2.0";
 
		GIFHeader _header;
 
		GIFBitmap(GIFHeader header)
		{
			_header = header;
			LoopCount = 0;
			Delay = 10;
		}
 
		void SetDisposeMethod(int flags)
		{
			Dispose = (DisposeMethod)((flags & 0x1C) >> 2);
			if (Dispose == DisposeMethod.NoAction)
				Dispose = DisposeMethod.LeaveInPlace;
		}
 
		void SetTransparency(int flags, int index)
		{
			IsTransparent = (flags & 1) != 0;
			TransparencyIndex = index;
		}
 
		void SetDelay(int delay)
		{
			// Convert to milliseconds.
			Delay = Math.Max(10, delay * 10);
		}
 
		bool UseLocalColorTable(int flags)
		{
			return ((flags & 0x80) != 0);
		}
 
		short LocalColorTableSize(int flags)
		{
			return (short)(Math.Pow(2, (flags & 0x07) + 1));
		}
 
		bool UseInterlace(int flags)
		{
			return ((flags & 0x40) != 0);
		}
 
		void ParseGraphicControlExtension(GIFDecoderStreamReader stream)
		{
			int blockSize = stream.Read();
			if (blockSize != 4)
				throw new GIFDecoderFormatException("Invalid graphic control extension size.");
 
			int flags = stream.Read();
			SetDisposeMethod(flags);
			SetDelay(stream.ReadShort());
			SetTransparency(flags, stream.Read());
 
			// Consume block terminator.
			stream.Read();
		}
 
		async Task ParseNetscapeApplicationExtensionAsync(GIFDecoderStreamReader stream)
		{
			int blockSize = await stream.ReadBlockAsync().ConfigureAwait(false);
			while (blockSize > 0)
			{
				if (stream.CurrentBlockBuffer[0] == 1 && blockSize == 3)
				{
					int count = (stream.CurrentBlockBuffer[2] << 8) | stream.CurrentBlockBuffer[1];
 
					if (count == 0)
						LoopCount = int.MaxValue;
					else if (count != 0)
						LoopCount = count;
				}
				blockSize = await stream.ReadBlockAsync().ConfigureAwait(false);
			}
		}
 
		async Task ParseApplicationExtensionAsync(GIFDecoderStreamReader stream)
		{
			var blockSize = await stream.ReadBlockAsync().ConfigureAwait(false);
			if (blockSize >= NetscapeApplicationExtensionID.Length)
			{
				var buffer = stream.CurrentBlockBuffer;
				string identifier = global::System.Text.Encoding.UTF8.GetString(buffer, 0, NetscapeApplicationExtensionID.Length);
				if (identifier.Equals(NetscapeApplicationExtensionID, StringComparison.OrdinalIgnoreCase))
				{
					await ParseNetscapeApplicationExtensionAsync(stream).ConfigureAwait(false);
					return;
				}
			}
			await stream.SkipBlockAsync().ConfigureAwait(false);
			return;
		}
 
		async Task ParseGIFBitmapHeaderAsync(GIFDecoderStreamReader stream)
		{
			Bounds = new GIFBitmap.Rect(stream.ReadShort(), stream.ReadShort(), stream.ReadShort(), stream.ReadShort());
			ColorTable = _header.GlobalColorTable;
 
			int flags = stream.Read();
			if (UseLocalColorTable(flags))
			{
				ColorTable = await GIFColorTable.CreateColorTableAsync(stream, LocalColorTableSize(flags)).ConfigureAwait(false);
			}
 
			BackgroundColor = _header.BackgroundColor;
			IsInterlaced = UseInterlace(flags);
		}
 
		async Task ParseImageDescriptorAsync(GIFDecoderStreamReader stream, GIFBitmapDecoder decoder, GIFBitmap previousBitmap, bool ignoreImageData)
		{
			await ParseGIFBitmapHeaderAsync(stream).ConfigureAwait(false);
			if (IsTransparent)
				ColorTable.SetTransparency(TransparencyIndex);
 
			DataPosition = stream.CurrentPosition;
 
			if (!ignoreImageData)
			{
				// Decode LZW data stream.
				await decoder.DecodeAsync(stream, _header.Width, _header.Height).ConfigureAwait(false);
 
				// Compose bitmap from decoded data stream.
				decoder.Compose(_header, this, previousBitmap);
 
				// Consume block terminator.
				await stream.SkipBlockAsync().ConfigureAwait(false);
			}
			else
			{
				// Read pass variable length LZW data stream.
				// First byte is LZW code size followed by data blocks repeated until block terminator.
				stream.Read();
				await stream.SkipBlockAsync().ConfigureAwait(false);
			}
 
			if (IsTransparent)
				ColorTable.ResetTransparency();
		}
 
		async Task ParseExtensionAsync(GIFDecoderStreamReader stream)
		{
			int blockCode = stream.Read();
			switch (blockCode)
			{
				case GIFBlockCodes.GraphicsControlExtension:
					ParseGraphicControlExtension(stream);
					break;
				case GIFBlockCodes.ApplicationExtensionBlock:
					await ParseApplicationExtensionAsync(stream).ConfigureAwait(false);
					break;
				default:
					await stream.SkipBlockAsync().ConfigureAwait(false);
					break;
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmap.xml" path="//Member[@MemberName='CreateBitmapAsync']/Docs/*" />
		public static async Task<GIFBitmap> CreateBitmapAsync(GIFDecoderStreamReader stream, GIFHeader header, GIFBitmapDecoder decoder, GIFBitmap previousBitmap, bool ignoreImageData = false)
		{
			GIFBitmap currentBitmap = null;
			bool haveImage = false;
			bool done = false;
 
			while (!done)
			{
				int blockCode = stream.Read();
				if (blockCode == -1)
				{
					currentBitmap = null;
					break;
				}
 
				switch (blockCode)
				{
					case GIFBlockCodes.ImageSeparator:
						if (currentBitmap == null)
							currentBitmap = new GIFBitmap(header);
						await currentBitmap.ParseImageDescriptorAsync(stream, decoder, previousBitmap, ignoreImageData).ConfigureAwait(false);
						haveImage = true;
						done = true;
						break;
					case GIFBlockCodes.Extension:
						if (currentBitmap == null)
							currentBitmap = new GIFBitmap(header);
						await currentBitmap.ParseExtensionAsync(stream).ConfigureAwait(false);
						break;
					case GIFBlockCodes.Trailer:
						done = true;
						if (!haveImage)
							currentBitmap = null;
						break;
					default:
						break;
				}
			}
 
			return currentBitmap;
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmapDecoder.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFBitmapDecoder']/Docs/*" />
	public class GIFBitmapDecoder
	{
		short[] _prefix;
		byte[] _suffix;
		byte[] _pixelStack;
		byte[] _pixels;
 
		const int DecoderStackSize = 4096;
 
		void InitializeBuffers(int pixelCount)
		{
			if (_pixels == null || _pixels.Length < pixelCount)
			{
				_pixels = new byte[pixelCount];
			}
			if (_prefix == null)
			{
				_prefix = new short[DecoderStackSize];
			}
			if (_suffix == null)
			{
				_suffix = new byte[DecoderStackSize];
			}
			if (_pixelStack == null)
			{
				_pixelStack = new byte[DecoderStackSize + 1];
			}
		}
 
		void RestoreToBackground(GIFHeader header, GIFBitmap currentBitmap, GIFBitmap previousBitmap, int[] bitmapData)
		{
			int color = 0;
			if (!currentBitmap.IsTransparent)
			{
				color = previousBitmap.BackgroundColor;
			}
 
			var previousBitmapBounds = previousBitmap.Bounds;
			for (int currentRow = 0; currentRow < previousBitmapBounds.Height; currentRow++)
			{
				int startBitmapIndex = (previousBitmapBounds.Y + currentRow) * header.Width + previousBitmapBounds.X;
				int endBitmapIndex = startBitmapIndex + previousBitmapBounds.Width;
				for (int currentBitmapIndex = startBitmapIndex; currentBitmapIndex < endBitmapIndex; currentBitmapIndex++)
				{
					bitmapData[currentBitmapIndex] = color;
				}
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmapDecoder.xml" path="//Member[@MemberName='Compose']/Docs/*" />
		public void Compose(GIFHeader header, GIFBitmap currentBitmap, GIFBitmap previousBitmap)
		{
			int[] bitmapData = null;
			var width = header.Width;
			var height = header.Height;
 
			if (previousBitmap != null && previousBitmap.Dispose != GIFBitmap.DisposeMethod.NoAction)
			{
				if (previousBitmap.Data != null)
				{
					bitmapData = previousBitmap.Data;
					if (previousBitmap.Dispose == GIFBitmap.DisposeMethod.RestoreToBackground)
					{
						RestoreToBackground(header, currentBitmap, previousBitmap, bitmapData);
					}
				}
			}
 
			// Reuse previous bitmap buffer or allocate new.
			if (bitmapData == null)
			{
				bitmapData = new int[width * height];
			}
 
			int interlacePass = 1;
			int interlaceRowInc = 8;
			int interlaceStartRow = 0;
			var bounds = currentBitmap.Bounds;
			var isInterlaced = currentBitmap.IsInterlaced;
			var colorTable = currentBitmap.ColorTable.Data;
 
			for (int sourceRow = 0; sourceRow < bounds.Height; sourceRow++)
			{
				int targetRow = sourceRow;
				if (isInterlaced)
				{
					if (interlaceStartRow >= bounds.Height)
					{
						interlacePass++;
						switch (interlacePass)
						{
							case 2:
								interlaceStartRow = 4;
								break;
							case 3:
								interlaceStartRow = 2;
								interlaceRowInc = 4;
								break;
							case 4:
								interlaceStartRow = 1;
								interlaceRowInc = 2;
								break;
							default:
								break;
						}
					}
					targetRow = interlaceStartRow;
					interlaceStartRow += interlaceRowInc;
				}
				targetRow += bounds.Y;
				if (targetRow < height)
				{
					int startBitmapIndex = targetRow * width;
					int currentBitmapIndex = startBitmapIndex + bounds.X;
					int endBitmapIndex = currentBitmapIndex + bounds.Width;
					if ((startBitmapIndex + width) < endBitmapIndex)
					{
						endBitmapIndex = startBitmapIndex + width;
					}
 
					int currentPixelIndex = sourceRow * bounds.Width;
					while (currentBitmapIndex < endBitmapIndex)
					{
						int colorIndex = _pixels[currentPixelIndex++];
						int color = colorTable[colorIndex];
						if (color != 0)
						{
							bitmapData[currentBitmapIndex] = color;
						}
						currentBitmapIndex++;
					}
				}
			}
 
			currentBitmap.Data = bitmapData;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFBitmapDecoder.xml" path="//Member[@MemberName='DecodeAsync']/Docs/*" />
		public async Task DecodeAsync(GIFDecoderStreamReader stream, int width, int height)
		{
			int pixelCount = width * height;
			InitializeBuffers(pixelCount);
 
			int nullCode = -1;
			int inCode = nullCode;
			int oldCode = nullCode;
			int currentCode = nullCode;
 
			int dataSize = stream.Read();
			int codeSize = dataSize + 1;
			int codeMask = (1 << codeSize) - 1;
 
			int clearCode = 1 << dataSize;
			int endOfInformationCode = clearCode + 1;
			int availableCode = clearCode + 2;
 
			for (currentCode = 0; currentCode < clearCode; currentCode++)
			{
				_prefix[currentCode] = 0;
				_suffix[currentCode] = (byte)currentCode;
			}
 
			int datum = 0;
			int bits = 0;
			int count = 0;
			int firstCode = 0;
			int currentStackIndex = 0;
			int currentPixelIndex = 0;
			int currentBitIndex = 0;
			int i = 0;
 
			for (i = 0; i < pixelCount;)
			{
				if (currentStackIndex == 0)
				{
					if (bits < codeSize)
					{
						if (count == 0)
						{
							count = await stream.ReadBlockAsync().ConfigureAwait(false);
							if (count <= 0)
							{
								break;
							}
							currentBitIndex = 0;
						}
						datum += (stream.CurrentBlockBuffer[currentBitIndex] << bits);
						bits += 8;
						currentBitIndex++;
						count--;
						continue;
					}
 
					currentCode = datum & codeMask;
					datum >>= codeSize;
					bits -= codeSize;
					if ((currentCode > availableCode) || (currentCode == endOfInformationCode))
					{
						break;
					}
 
					if (currentCode == clearCode)
					{
						codeSize = dataSize + 1;
						codeMask = (1 << codeSize) - 1;
						availableCode = clearCode + 2;
						oldCode = nullCode;
						continue;
					}
 
					if (oldCode == nullCode)
					{
						_pixelStack[currentStackIndex++] = _suffix[currentCode];
						oldCode = currentCode;
						firstCode = currentCode;
						continue;
					}
 
					inCode = currentCode;
					if (currentCode == availableCode)
					{
						_pixelStack[currentStackIndex++] = (byte)firstCode;
						currentCode = oldCode;
					}
 
					while (currentCode > clearCode)
					{
						_pixelStack[currentStackIndex++] = _suffix[currentCode];
						currentCode = _prefix[currentCode];
					}
 
					firstCode = _suffix[currentCode];
					if (availableCode >= DecoderStackSize)
					{
						break;
					}
 
					_pixelStack[currentStackIndex++] = (byte)firstCode;
					_prefix[availableCode] = (short)oldCode;
					_suffix[availableCode] = (byte)firstCode;
					availableCode++;
					if (((availableCode & codeMask) == 0) && (availableCode < DecoderStackSize))
					{
						codeSize++;
						codeMask += availableCode;
					}
					oldCode = inCode;
				}
				currentStackIndex--;
				_pixels[currentPixelIndex++] = _pixelStack[currentStackIndex];
				i++;
			}
 
			for (i = currentPixelIndex; i < pixelCount; i++)
			{
				_pixels[i] = 0;
			}
		}
	}
 
	/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFImageParser.xml" path="Type[@FullName='Microsoft.Maui.Controls.Internals.GIFImageParser']/Docs/*" />
	public abstract class GIFImageParser
	{
		protected abstract void StartParsing();
		protected abstract void AddBitmap(GIFHeader header, GIFBitmap bitmap, bool ignoreImageData);
		protected abstract void FinishedParsing();
 
		/// <include file="../../../docs/Microsoft.Maui.Controls.Internals/GIFImageParser.xml" path="//Member[@MemberName='ParseAsync']/Docs/*" />
		public async Task ParseAsync(Stream stream, bool skipTypeIdentifier = false, bool ignoreImageData = false)
		{
			if (stream != null)
			{
				GIFBitmap previousBitmap = null;
				GIFBitmap currentBitmap = null;
 
				GIFBitmapDecoder decoder = new GIFBitmapDecoder();
				GIFDecoderStreamReader reader = new GIFDecoderStreamReader(stream);
 
				StartParsing();
 
				GIFHeader header = await GIFHeader.CreateHeaderAsync(reader, skipTypeIdentifier).ConfigureAwait(false);
 
				currentBitmap = await GIFBitmap.CreateBitmapAsync(reader, header, decoder, previousBitmap, ignoreImageData).ConfigureAwait(false);
				while (currentBitmap != null)
				{
					AddBitmap(header, currentBitmap, ignoreImageData);
					previousBitmap = currentBitmap;
					currentBitmap = await GIFBitmap.CreateBitmapAsync(reader, header, decoder, previousBitmap, ignoreImageData).ConfigureAwait(false);
				}
 
				FinishedParsing();
			}
			else
			{
				throw new ArgumentNullException(nameof(stream));
			}
		}
	}
}