첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

728x90
반응형

■ XAML에서 움직이는 GIF 이미지 표시하기

------------------------------------------------------------------------------------------------------------------------


TestSolution.zip



[TestLibrary 프로젝트]

 

GIFApplicationExtension.cs

 

 

using System;

using System.IO;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 애플리케이션 확장

    /// </summary>

    public class GIFApplicationExtension : GIFExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 확장 레이블

        /// </summary>

        public const int ExtensionLabel = 0xff;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 블럭 크기 - BlockSize

 

        /// <summary>

        /// 블럭 크기

        /// </summary>

        public int BlockSize { get; private set; }

 

        #endregion

        #region 애플리케이션 식별자 - ApplicationIdentifier

 

        /// <summary>

        /// 애플리케이션 식별자

        /// </summary>

        public string ApplicationIdentifier { get; private set; }

 

        #endregion

        #region 인증 코드 - AuthenticationCode

 

        /// <summary>

        /// 인증 코드

        /// </summary>

        public byte[] AuthenticationCode { get; private set; }

 

        #endregion

        #region 데이터 - Data

 

        /// <summary>

        /// 데이터

        /// </summary>

        public byte[] Data { get; private set; }

 

        #endregion

 

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.SpecialPurpose;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFApplicationExtension()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFApplicationExtension()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 애플리케이션 확장 태스크</returns>

        public static async Task<GIFApplicationExtension> ReadAsync(Stream stream)

        {

            GIFApplicationExtension extension = new GIFApplicationExtension();

 

            await extension.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return extension;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            byte[] byteArray = new byte[12];

 

            await stream.ReadAllAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);

 

            BlockSize = byteArray[0];

 

            if(BlockSize != 11)

            {

                throw GIFHelper.ThrowInvalidBlockSizeException("Application Extension", 11, BlockSize);

            }

 

            ApplicationIdentifier = GIFHelper.GetString(byteArray, 1, 8);

 

            byte[] authenticationCode = new byte[3];

 

            Array.Copy(byteArray, 9, authenticationCode, 0, 3);

 

            AuthenticationCode = authenticationCode;

 

            Data = await GIFHelper.ReadDataBlocksAsync(stream).ConfigureAwait(false);

        }

 

        #endregion

    }

}

 

 

GIFBlock.cs

 

 

using System.Collections.Generic;

using System.IO;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 블럭

    /// </summary>

    public abstract class GIFBlock

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public abstract GIFBlockKind Kind { get; }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>GIF 블럭 태스크</returns>

        public static async Task<GIFBlock> ReadAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            int blockID = await stream.ReadByteAsync().ConfigureAwait(false);

 

            if(blockID < 0)

            {

                throw new EndOfStreamException();

            }

 

            return blockID switch

            {

                GIFExtension.ExtensionIntroducer => await GIFExtension.ReadAsync(stream, extensionEnumerable).ConfigureAwait(false),

                GIFFrame.ImageSeparator => await GIFFrame.ReadAsync(stream, extensionEnumerable).ConfigureAwait(false),

                GIFTrailer.TrailerByte => await GIFTrailer.ReadAsync().ConfigureAwait(false),

                _ => throw GIFHelper.ThrowUnknownBlockTypeException(blockID),

            };

        }

 

        #endregion

    }

}

 

 

GIFBlockKind.cs

 

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 블럭 종류

    /// </summary>

    public enum GIFBlockKind

    {

        /// <summary>

        /// 컨트롤

        /// </summary>

        Control,

 

        /// <summary>

        /// 그래픽 렌더링

        /// </summary>

        GraphicRendering,

 

        /// <summary>

        /// 특수 목적

        /// </summary>

        SpecialPurpose,

 

        /// <summary>

        /// 기타

        /// </summary>

        Other

    }

}

 

 

GIFColor.cs

 

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 색상

    /// </summary>

    public struct GIFColor

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 적색 - Red

 

        /// <summary>

        /// 적색

        /// </summary>

        public byte Red { get; }

 

        #endregion

        #region 녹색 - Green

 

        /// <summary>

        /// 녹색

        /// </summary>

        public byte Green { get; }

 

        #endregion

        #region 청색 - Blue

 

        /// <summary>

        /// 청색

        /// </summary>

        public byte Blue { get; }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - GIFColor(red, green, blue)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="red">적색</param>

        /// <param name="green">녹색</param>

        /// <param name="blue">청색</param>

        public GIFColor(byte red, byte green, byte blue)

        {

            Red   = red;

            Green = green;

            Blue  = blue;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 문자열 구하기 - ToString()

 

        /// <summary>

        /// 문자열 구하기

        /// </summary>

        /// <returns>문자열</returns>

        public override string ToString()

        {

            return $"#{Red:x2}{Green:x2}{Blue:x2}";

        }

 

        #endregion

    }

}

 

 

GIFCommentExtension.cs

 

 

using System.IO;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 주석 확장

    /// </summary>

    public class GIFCommentExtension : GIFExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 확장 레이블

        /// </summary>

        public const int ExtensionLabel = 0xfe;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 텍스트 - Text

 

        /// <summary>

        /// 텍스트

        /// </summary>

        public string Text { get; private set; }

 

        #endregion

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.SpecialPurpose;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFCommentExtension()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFCommentExtension()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 주석 확장 태스크</returns>

        public static async Task<GIFCommentExtension> ReadAsync(Stream stream)

        {

            GIFCommentExtension extension = new GIFCommentExtension();

 

            await extension.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return extension;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            byte[] byteArray = await GIFHelper.ReadDataBlocksAsync(stream).ConfigureAwait(false);

 

            if(byteArray != null)

            {

                Text = GIFHelper.GetString(byteArray);

            }

        }

 

        #endregion

    }

}

 

 

GIFDataStream.cs

 

 

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 데이터 스트림

    /// </summary>

    public class GIFDataStream

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 헤더 - Header

 

        /// <summary>

        /// 헤더

        /// </summary>

        public GIFHeader Header { get; private set; }

 

        #endregion

        #region 전역 색상 테이블 - GlobalColorTable

 

        /// <summary>

        /// 전역 색상 테이블

        /// </summary>

        public GIFColor[] GlobalColorTable { get; set; }

 

        #endregion

        #region GIF 프레임 리스트 - FrameList

 

        /// <summary>

        /// GIF 프레임 리스트

        /// </summary>

        public IList<GIFFrame> FrameList { get; set; }

 

        #endregion

        #region GIF 확장 리스트 - ExtensionList

 

        /// <summary>

        /// GIF 확장 리스트

        /// </summary>

        public IList<GIFExtension> ExtensionList { get; set; }

 

        #endregion

        #region 반복 카운트 - RepeatCount

 

        /// <summary>

        /// 반복 카운트

        /// </summary>

        public ushort RepeatCount { get; set; }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFDataStream()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFDataStream()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 데이터 스트림 태스크</returns>

        public static async Task<GIFDataStream> ReadAsync(Stream stream)

        {

            GIFDataStream dataStream = new GIFDataStream();

 

            await dataStream.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return dataStream;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 프레임 읽기 (비동기) - ReadFramesAsync(stream)

 

        /// <summary>

        /// 프레임 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadFramesAsync(Stream stream)

        {

            List<GIFFrame>     frameList            = new List<GIFFrame>();

            List<GIFExtension> controlExtensionList = new List<GIFExtension>();

            List<GIFExtension> specialExtensionList = new List<GIFExtension>();

 

            while(true)

            {

                try

                {

                    GIFBlock block = await GIFBlock.ReadAsync(stream, controlExtensionList).ConfigureAwait(false);

 

                    if(block.Kind == GIFBlockKind.GraphicRendering)

                    {

                        controlExtensionList = new List<GIFExtension>();

                    }

 

                    if(block is GIFFrame frame)

                    {

                        frameList.Add(frame);

                    }

                    else if(block is GIFExtension extension)

                    {

                        switch(extension.Kind)

                        {

                            case GIFBlockKind.Control :

 

                                controlExtensionList.Add(extension);

 

                                break;

 

                            case GIFBlockKind.SpecialPurpose :

 

                                specialExtensionList.Add(extension);

 

                                break;

                        }

                    }

                    else if(block is GIFTrailer)

                    {

                        break;

                    }

                }

                catch(UnknownBlockTypeException) when (frameList.Count > 0)

                {

                    break;

                }

            }

 

            this.FrameList     = frameList.AsReadOnly();

            this.ExtensionList = specialExtensionList.AsReadOnly();

        }

 

        #endregion

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            Header = await GIFHeader.ReadAsync(stream).ConfigureAwait(false);

 

            if(Header.LogicalScreenDescriptor.HasGlobalColorTable)

            {

                GlobalColorTable = await GIFHelper.ReadColorTableAsync

                (

                    stream,

                    Header.LogicalScreenDescriptor.GlobalColorTableSize

                ).ConfigureAwait(false);

            }

 

            await ReadFramesAsync(stream).ConfigureAwait(false);

 

            GIFApplicationExtension extension = ExtensionList.OfType<GIFApplicationExtension>()

                .FirstOrDefault(GIFHelper.IsNetscapeExtension);

 

            RepeatCount = extension != null ? GIFHelper.GetRepeatCount(extension) : (ushort)1;

        }

 

        #endregion

    }

}

 

 

GIFDecoderException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 디코더 예외

    /// </summary>

    [Serializable]

    public abstract class GIFDecoderException : Exception

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - GIFDecoderException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        protected GIFDecoderException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - GIFDecoderException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        protected GIFDecoderException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

        #region 생성자 - GIFDecoderException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected GIFDecoderException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

GIFExtension.cs

 

 

using System.Collections.Generic;

using System.IO;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 확장

    /// </summary>

    public abstract class GIFExtension : GIFBlock

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 확장 소개자

        /// </summary>

        public const int ExtensionIntroducer = 0x21;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>GIF 확장 태스크</returns>

        public new static async Task<GIFExtension> ReadAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            int label = stream.ReadByte();

 

            if(label < 0)

            {

                throw new EndOfStreamException();

            }

 

            return label switch

            {

                GIFGraphicControlExtension.ExtensionLabel => await GIFGraphicControlExtension.ReadAsync(stream).ConfigureAwait(false),

                GIFCommentExtension.ExtensionLabel => await GIFCommentExtension.ReadAsync(stream).ConfigureAwait(false),

                GIFPlainTextExtension.ExtensionLabel => await GIFPlainTextExtension.ReadAsync(stream, extensionEnumerable)

                                                                                   .ConfigureAwait(false),

                GIFApplicationExtension.ExtensionLabel => await GIFApplicationExtension.ReadAsync(stream).ConfigureAwait(false),

                _ => throw GIFHelper.ThrowUnknownExtensionTypeException(label),

            };

        }

 

        #endregion

    }

}

 

 

GIFFrame.cs

 

 

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 프레임

    /// </summary>

    public class GIFFrame : GIFBlock

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 이미지 분리자

        /// </summary>

        public const int ImageSeparator = 0x2C;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region GIF 이미지 설명자 - Descriptor

 

        /// <summary>

        /// GIF 이미지 설명자

        /// </summary>

        public GIFImageDescriptor Descriptor { get; private set; }

 

        #endregion

        #region 지역 색상 테이블 - LocalColorTable

 

        /// <summary>

        /// 지역 색상 테이블

        /// </summary>

        public GIFColor[] LocalColorTable { get; private set; }

 

        #endregion

        #region GIF 확장 리스트 - ExtensionList

 

        /// <summary>

        /// GIF 확장 리스트

        /// </summary>

        public IList<GIFExtension> ExtensionList { get; private set; }

 

        #endregion

        #region GIF 이미지 데이터 - ImageData

 

        /// <summary>

        /// GIF 이미지 데이터

        /// </summary>

        public GIFImageData ImageData { get; private set; }

 

        #endregion

        #region GIF 그래픽 컨트롤 확장 - GraphicControl

 

        /// <summary>

        /// GIF 그래픽 컨트롤 확장

        /// </summary>

        public GIFGraphicControlExtension GraphicControl { get; set; }

 

        #endregion

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.GraphicRendering;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFFrame()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFFrame()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>GIF 프레임 태스크</returns>

        public new static async Task<GIFFrame> ReadAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            GIFFrame frame = new GIFFrame();

 

            await frame.ReadInternalAsync(stream, extensionEnumerable).ConfigureAwait(false);

 

            return frame;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            Descriptor = await GIFImageDescriptor.ReadAsync(stream).ConfigureAwait(false);

 

            if(Descriptor.HasLocalColorTable)

            {

                LocalColorTable = await GIFHelper.ReadColorTableAsync

                (

                    stream,

                    Descriptor.LocalColorTableSize

                ).ConfigureAwait(false);

            }

 

            ImageData = await GIFImageData.ReadAsync(stream).ConfigureAwait(false);

 

            ExtensionList  = extensionEnumerable.ToList().AsReadOnly();

 

            GraphicControl = ExtensionList.OfType<GIFGraphicControlExtension>().FirstOrDefault();

        }

 

        #endregion

    }

}

 

 

GIFFrameDisposalMethod.cs

 

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 프레임 처분 메소드

    /// </summary>

    public enum GIFFrameDisposalMethod

    {

        /// <summary>

        /// None

        /// </summary>

        None = 0,

 

        /// <summary>

        /// Do Not Dispose

        /// </summary>

        DoNotDispose = 1,

 

        /// <summary>

        /// Restore Background

        /// </summary>

        RestoreBackground = 2,

 

        /// <summary>

        /// Restore Previous

        /// </summary>

        RestorePrevious = 3

    }

}

 

 

GIFGraphicControlExtension.cs

 

 

using System;

using System.IO;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 그래픽 컨트롤 확장

    /// </summary>

    public class GIFGraphicControlExtension : GIFExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 확장 레이블

        /// </summary>

        public const int ExtensionLabel = 0xf9;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 블럭 크기 - BlockSize

 

        /// <summary>

        /// 블럭 크기

        /// </summary>

        public int BlockSize { get; private set; }

 

        #endregion

        #region GIF 프레임 처분 메소드 - DisposalMethod

 

        /// <summary>

        /// GIF 프레임 처분 메소드

        /// </summary>

        public GIFFrameDisposalMethod DisposalMethod { get; private set; }

 

        #endregion

        #region 사용자 입력 - UserInput

 

        /// <summary>

        /// 사용자 입력

        /// </summary>

        public bool UserInput { get; private set; }

 

        #endregion

        #region 투명 여부 - HasTransparency

 

        /// <summary>

        /// 투명 여부

        /// </summary>

        public bool HasTransparency { get; private set; }

 

        #endregion

        #region 지연 - Delay

 

        /// <summary>

        /// 지연

        /// </summary>

        public int Delay { get; private set; }

 

        #endregion

        #region 투명 인덱스 - TransparencyIndex

 

        /// <summary>

        /// 투명 인덱스

        /// </summary>

        public int TransparencyIndex { get; private set; }

 

        #endregion

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.Control;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFGraphicControlExtension()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFGraphicControlExtension()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 그래픽 컨트롤 확장 태스크</returns>

        public static async Task<GIFGraphicControlExtension> ReadAsync(Stream stream)

        {

            GIFGraphicControlExtension extension = new GIFGraphicControlExtension();

 

            await extension.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return extension;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            byte[] byteArray = new byte[6];

 

            await stream.ReadAllAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);

 

            BlockSize = byteArray[0];

 

            if(BlockSize != 4)

            {

                throw GIFHelper.ThrowInvalidBlockSizeException("Graphic Control Extension", 4, BlockSize);

            }

 

            byte packedField = byteArray[1];

 

            DisposalMethod = (GIFFrameDisposalMethod) ((packedField & 0x1C) >> 2);

 

            UserInput = (packedField & 0x02) != 0;

 

            HasTransparency = (packedField & 0x01) != 0;

 

            Delay = BitConverter.ToUInt16(byteArray, 2) * 10;

 

            TransparencyIndex = byteArray[4];

        }

 

        #endregion

    }

}

 

 

GIFHeader.cs

 

 

using System.IO;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 헤더

    /// </summary>

    public class GIFHeader : GIFBlock

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 서명 - Signature

 

        /// <summary>

        /// 서명

        /// </summary>

        public string Signature { get; private set; }

 

        #endregion

        #region 버전 - Version

 

        /// <summary>

        /// 버전

        /// </summary>

        public string Version { get; private set; }

 

        #endregion

        #region GIF 논리 화면 설명자 - LogicalScreenDescriptor

 

        /// <summary>

        /// GIF 논리 화면 설명자

        /// </summary>

        public GIFLogicalScreenDescriptor LogicalScreenDescriptor { get; private set; }

 

        #endregion

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.Other;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFHeader()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFHeader()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 헤더 태스크</returns>

        public static async Task<GIFHeader> ReadAsync(Stream stream)

        {

            GIFHeader header = new GIFHeader();

 

            await header.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return header;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            Signature = await GIFHelper.ReadStringAsync(stream, 3).ConfigureAwait(false);

 

            if(Signature != "GIF")

            {

                throw GIFHelper.ThrowInvalidSignatureException(Signature);

            }

 

            Version = await GIFHelper.ReadStringAsync(stream, 3).ConfigureAwait(false);

 

            if(Version != "87a" && Version != "89a")

            {

                throw GIFHelper.ThrowUnsupportedVersionException(Version);

            }

 

            LogicalScreenDescriptor = await GIFLogicalScreenDescriptor.ReadAsync(stream).ConfigureAwait(false);

        }

 

        #endregion

    }

}

 

 

GIFHelper.cs

 

 

using System;

using System.IO;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 헬퍼

    /// </summary>

    public static class GIFHelper

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 문자열 읽기 (비동기) - ReadStringAsync(stream, length)

 

        /// <summary>

        /// 문자열 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="length">길이</param>

        /// <returns>문자열 태스크</returns>

        public static async Task<string> ReadStringAsync(Stream stream, int length)

        {

            byte[] byteArray = new byte[length];

 

            await stream.ReadAllAsync(byteArray, 0, length).ConfigureAwait(false);

 

            return GetString(byteArray);

        }

 

        #endregion

        #region 스트림에 데이터 블럭 복사하기 (비동기) - CopyDataBlocksToStreamAsync(sourceStream, targetStream, cancellationToken)

 

        /// <summary>

        /// 스트림에 데이터 블럭 복사하기 (비동기)

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="targetStream">타겟 스트림</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>태스크</returns>

        public static async Task CopyDataBlocksToStreamAsync

        (

            Stream            sourceStream,

            Stream            targetStream,

            CancellationToken cancellationToken = default

        )

        {

            int length;

 

            byte[] buffer = new byte[255];

 

            while((length = await sourceStream.ReadByteAsync(cancellationToken)) > 0)

            {

                await sourceStream.ReadAllAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false);

 

#if LACKS_STREAM_MEMORY_OVERLOADS

 

                await targetStream.WriteAsync(buffer, 0, length, cancellationToken);

#else

                await targetStream.WriteAsync(buffer.AsMemory(0, length), cancellationToken);

#endif

            }

        }

 

        #endregion

        #region 데이터 블럭 소비하기 (비동기) - ConsumeDataBlocksAsync(stream, cancellationToken)

 

        /// <summary>

        /// 데이터 블럭 소비하기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>태스크</returns>

        public static async Task ConsumeDataBlocksAsync(Stream stream, CancellationToken cancellationToken = default)

        {

            await CopyDataBlocksToStreamAsync(stream, Stream.Null, cancellationToken);

        }

 

        #endregion

        #region 데이터 블럭 읽기 (비동기) - ReadDataBlocksAsync(sourceStream, cancellationToken)

 

        /// <summary>

        /// 데이터 블럭 읽기 (비동기)

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>바이트 배열 태스크</returns>

        public static async Task<byte[]> ReadDataBlocksAsync(Stream sourceStream, CancellationToken cancellationToken = default)

        {

            using MemoryStream targetStream = new MemoryStream();

 

            await CopyDataBlocksToStreamAsync(sourceStream, targetStream, cancellationToken);

 

            return targetStream.ToArray();

        }

 

        #endregion

        #region 색상 테이블 읽기 (비동기) - ReadColorTableAsync(stream, size)

 

        /// <summary>

        /// 색상 테이블 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="size">크기</param>

        /// <returns>GIF 색상 배열 태스크</returns>

        public static async Task<GIFColor[]> ReadColorTableAsync(Stream stream, int size)

        {

            int    length    = 3 * size;

            byte[] byteArray = new byte[length];

 

            await stream.ReadAllAsync(byteArray, 0, length).ConfigureAwait(false);

 

            GIFColor[] colorTable = new GIFColor[size];

 

            for(int i = 0; i < size; i++)

            {

                byte red   = byteArray[3 * i    ];

                byte green = byteArray[3 * i + 1];

                byte blue  = byteArray[3 * i + 2];

 

                colorTable[i] = new GIFColor(red, green, blue);

            }

 

            return colorTable;

        }

 

        #endregion

        #region 넷스케이프 확장 여부 구하기 - IsNetscapeExtension(extension)

 

        /// <summary>

        /// 넷스케이프 확장 여부 구하기

        /// </summary>

        /// <param name="extension">GIF 애플리케이션 확장</param>

        /// <returns>넷스케이프 확장 여부</returns>

        public static bool IsNetscapeExtension(GIFApplicationExtension extension)

        {

            return extension.ApplicationIdentifier == "NETSCAPE" && GetString(extension.AuthenticationCode) == "2.0";

        }

 

        #endregion

        #region 반복 카운트 구하기 - GetRepeatCount(extension)

 

        /// <summary>

        /// 반복 카운트 구하기

        /// </summary>

        /// <param name="extension">GIF 애플리케이션 확장</param>

        /// <returns>반복 카운트</returns>

        public static ushort GetRepeatCount(GIFApplicationExtension extension)

        {

            if(extension.Data.Length >= 3)

            {

                return BitConverter.ToUInt16(extension.Data, 1);

            }

 

            return 1;

        }

 

        #endregion

        #region 알 수 없는 블럭 타입 예외 던지기 - ThrowUnknownBlockTypeException(blockID)

 

        /// <summary>

        /// 알 수 없는 블럭 타입 예외 던지기

        /// </summary>

        /// <param name="blockID">블럭 ID</param>

        /// <returns>예외</returns>

        public static Exception ThrowUnknownBlockTypeException(int blockID)

        {

            return new UnknownBlockTypeException("Unknown block type : 0x" + blockID.ToString("x2"));

        }

 

        #endregion

        #region 알 수 없는 확장 타입 예외 던지기 - ThrowUnknownExtensionTypeException(extensionLabel)

 

        /// <summary>

        /// 알 수 없는 확장 타입 예외 던지기

        /// </summary>

        /// <param name="extensionLabel">확장 레이블</param>

        /// <returns>예외</returns>

        public static Exception ThrowUnknownExtensionTypeException(int extensionLabel)

        {

            return new UnknownExtensionTypeException("Unknown extension type : 0x" + extensionLabel.ToString("x2"));

        }

 

        #endregion

        #region 무효 블럭 크기 예외 던지기 - ThrowInvalidBlockSizeException(blockName, expectedBlockSize, actualBlockSize)

 

        /// <summary>

        /// 무효 블럭 크기 예외 던지기

        /// </summary>

        /// <param name="blockName">블럭명</param>

        /// <param name="expectedBlockSize">기대 블럭 크기</param>

        /// <param name="actualBlockSize">실제 블럭 크기</param>

        /// <returns>예외</returns>

        public static Exception ThrowInvalidBlockSizeException(string blockName, int expectedBlockSize, int actualBlockSize)

        {

            return new InvalidBlockSizeException

            (

                $"Invalid block size for {blockName}. Expected {expectedBlockSize}, but was {actualBlockSize}"

            );

        }

 

        #endregion

        #region 무효 서명 예외 던지기 - ThrowInvalidSignatureException(signature)

 

        /// <summary>

        /// 무효 서명 예외 던지기

        /// </summary>

        /// <param name="signature">서명</param>

        /// <returns>예외</returns>

        public static Exception ThrowInvalidSignatureException(string signature)

        {

            return new InvalidSignatureException("Invalid file signature : " + signature);

        }

 

        #endregion

        #region 미지원 버전 예외 던지기 - ThrowUnsupportedVersionException(version)

 

        /// <summary>

        /// 미지원 버전 예외 던지기

        /// </summary>

        /// <param name="version">버전</param>

        /// <returns>예외</returns>

        public static Exception ThrowUnsupportedVersionException(string version)

        {

            return new UnsupportedGIFVersionException("Unsupported version : " + version);

        }

 

        #endregion

        #region 문자열 구하기 - GetString(sourceByteArray, index, count)

 

        /// <summary>

        /// 문자열 구하기

        /// </summary>

        /// <param name="sourceByteArray">소스 바이트 배열</param>

        /// <param name="index">인덱스</param>

        /// <param name="count">카운트</param>

        /// <returns>문자열</returns>

        public static string GetString(byte[] sourceByteArray, int index, int count)

        {

            return Encoding.UTF8.GetString(sourceByteArray, index, count);

        }

 

        #endregion

        #region 문자열 구하기 - GetString(sourceByteArray)

 

        /// <summary>

        /// 문자열 구하기

        /// </summary>

        /// <param name="sourceByteArray">소스 바이트 배열</param>

        /// <returns>문자열</returns>

        public static string GetString(byte[] sourceByteArray)

        {

            return GetString(sourceByteArray, 0, sourceByteArray.Length);

        }

 

        #endregion

    }

}

 

 

GIFImageData.cs

 

 

using System.IO;

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 이미지 데이터

    /// </summary>

    public class GIFImageData

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region LZW 최소 코드 크기 - LZWMinimumCodeSize

 

        /// <summary>

        /// LZW 최소 코드 크기

        /// </summary>

        public byte LZWMinimumCodeSize { get; set; }

 

        #endregion

        #region 압축 데이터 시작 오프셋 - CompressedDataStartOffset

 

        /// <summary>

        /// 압축 데이터 시작 오프셋

        /// </summary>

        public long CompressedDataStartOffset { get; set; }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFImageData()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFImageData()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 이미지 데이터 태스크</returns>

        public static async Task<GIFImageData> ReadAsync(Stream stream)

        {

            GIFImageData imageData = new GIFImageData();

 

            await imageData.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return imageData;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            LZWMinimumCodeSize = (byte)stream.ReadByte();

 

            CompressedDataStartOffset = stream.Position;

 

            await GIFHelper.ConsumeDataBlocksAsync(stream).ConfigureAwait(false);

        }

 

        #endregion

    }

}

 

 

GIFImageDescriptor.cs

 

 

using System;

using System.IO;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 이미지 설명자

    /// </summary>

    public class GIFImageDescriptor : IGIFRectangle

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 왼쪽

 

        /// <summary>

        /// 왼쪽

        /// </summary>

        public int Left { get; private set; }

 

        #endregion

        #region 위쪽

 

        /// <summary>

        /// 위쪽

        /// </summary>

        public int Top { get; private set; }

 

        #endregion

        #region 너비

 

        /// <summary>

        /// 너비

        /// </summary>

        public int Width { get; private set; }

 

        #endregion

        #region 높이

 

        /// <summary>

        /// 높이

        /// </summary>

        public int Height { get; private set; }

 

        #endregion

        #region 지역 색상 테이블 보유 여부

 

        /// <summary>

        /// 지역 색상 테이블 보유 여부

        /// </summary>

        public bool HasLocalColorTable { get; private set; }

 

        #endregion

        #region 인터레이스 - Interlace

 

        /// <summary>

        /// 인터레이스

        /// </summary>

        public bool Interlace { get; private set; }

 

        #endregion

        #region 지역 색상 테이블 정렬 여부 - IsLocalColorTableSorted

 

        /// <summary>

        /// 지역 색상 테이블 정렬 여부

        /// </summary>

        public bool IsLocalColorTableSorted { get; private set; }

 

        #endregion

        #region 지역 색상 테이블 크기 - LocalColorTableSize

 

        /// <summary>

        /// 지역 색상 테이블 크기

        /// </summary>

        public int LocalColorTableSize { get; private set; }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFImageDescriptor()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFImageDescriptor()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 이미지 설명자 태스크</returns>

        public static async Task<GIFImageDescriptor> ReadAsync(Stream stream)

        {

            GIFImageDescriptor descriptor = new GIFImageDescriptor();

 

            await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return descriptor;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            byte[] byteArray = new byte[9];

 

            await stream.ReadAllAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);

 

            Left   = BitConverter.ToUInt16(byteArray, 0);

            Top    = BitConverter.ToUInt16(byteArray, 2);

            Width  = BitConverter.ToUInt16(byteArray, 4);

            Height = BitConverter.ToUInt16(byteArray, 6);

 

            byte packedField = byteArray[8];

 

            HasLocalColorTable = (packedField & 0x80) != 0;

 

            Interlace = (packedField & 0x40) != 0;

 

            IsLocalColorTableSorted = (packedField & 0x20) != 0;

 

            LocalColorTableSize = 1 << ((packedField & 0x07) + 1);

        }

 

        #endregion

    }

}

 

 

GIFLogicalScreenDescriptor.cs

 

 

using System;

using System.IO;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 논리 화면 설명자

    /// </summary>

    public class GIFLogicalScreenDescriptor : IGIFRectangle

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 너비 - Width

 

        /// <summary>

        /// 너비

        /// </summary>

        public int Width { get; private set; }

 

        #endregion

        #region 높이 - Height

 

        /// <summary>

        /// 높이

        /// </summary>

        public int Height { get; private set; }

 

        #endregion

        #region 전역 색상 테이블 보유 여부 - HasGlobalColorTable

 

        /// <summary>

        /// 전역 색상 테이블 보유 여부

        /// </summary>

        public bool HasGlobalColorTable { get; private set; }

 

        #endregion

        #region 색상 해상도 - ColorResolution

 

        /// <summary>

        /// 색상 해상도

        /// </summary>

        public int ColorResolution { get; private set; }

 

        #endregion

        #region 전역 색상 테이블 정렬 여부 - IsGlobalColorTableSorted

 

        /// <summary>

        /// 전역 색상 테이블 정렬 여부

        /// </summary>

        public bool IsGlobalColorTableSorted { get; private set; }

 

        #endregion

        #region 전역 색상 테이블 크기 - GlobalColorTableSize

 

        /// <summary>

        /// 전역 색상 테이블 크기

        /// </summary>

        public int GlobalColorTableSize { get; private set; }

 

        #endregion

        #region 배경 색상 인덱스 - BackgroundColorIndex

 

        /// <summary>

        /// 배경 색상 인덱스

        /// </summary>

        public int BackgroundColorIndex { get; private set; }

 

        #endregion

        #region 픽셀 종횡비 - PixelAspectRatio

 

        /// <summary>

        /// 픽셀 종횡비

        /// </summary>

        public double PixelAspectRatio { get; private set; }

 

        #endregion

 

        #region 왼쪽 - IGIFRectangle.Left

 

        /// <summary>

        /// 왼쪽

        /// </summary>

        int IGIFRectangle.Left

        {

            get

            {

                return 0;

            }

        }

 

        #endregion

        #region 위쪽 - IGIFRectangle.Top

 

        /// <summary>

        /// 위쪽

        /// </summary>

        int IGIFRectangle.Top

        {

            get

            {

                return 0;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>GIF 논리 화면 설명자 태스크</returns>

        public static async Task<GIFLogicalScreenDescriptor> ReadAsync(Stream stream)

        {

            GIFLogicalScreenDescriptor descriptor = new GIFLogicalScreenDescriptor();

 

            await descriptor.ReadInternalAsync(stream).ConfigureAwait(false);

 

            return descriptor;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream)

        {

            byte[] byteArray = new byte[7];

 

            await stream.ReadAllAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);

 

            Width  = BitConverter.ToUInt16(byteArray, 0);

            Height = BitConverter.ToUInt16(byteArray, 2);

 

            byte packedField = byteArray[4];

 

            HasGlobalColorTable = (packedField & 0x80) != 0;

 

            ColorResolution = ((packedField & 0x70) >> 4) + 1;

 

            IsGlobalColorTableSorted = (packedField & 0x08) != 0;

 

            GlobalColorTableSize = 1 << ((packedField & 0x07) + 1);

 

            BackgroundColorIndex = byteArray[5];

 

            PixelAspectRatio = byteArray[6] == 0 ? 0.0 : (15 + byteArray[6]) / 64.0;

        }

 

        #endregion

    }

}

 

 

GIFPlainTextExtension.cs

 

 

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Threading.Tasks;

 

using TestLibrary.Extension;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 평문 텍스트 확장

    /// </summary>

    public class GIFPlainTextExtension : GIFExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 확장 레이블

        /// </summary>

        public const int ExtensionLabel = 0x01;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 블럭 크기 - BlockSize

 

        /// <summary>

        /// 블럭 크기

        /// </summary>

        public int BlockSize { get; private set; }

 

        #endregion

        #region 왼쪽 - Left

 

        /// <summary>

        /// 왼쪽

        /// </summary>

        public int Left { get; private set; }

 

        #endregion

        #region 위쪽 - Top

 

        /// <summary>

        /// 위쪽

        /// </summary>

        public int Top { get; private set; }

 

        #endregion

        #region 너비 - Width

 

        /// <summary>

        /// 너비

        /// </summary>

        public int Width { get; private set; }

 

        #endregion

        #region 높이 - Height

 

        /// <summary>

        /// 높이

        /// </summary>

        public int Height { get; private set; }

 

        #endregion

        #region 셀 너비 - CellWidth

 

        /// <summary>

        /// 셀 너비

        /// </summary>

        public int CellWidth { get; private set; }

 

        #endregion

        #region 셀 높이 - CellHeight

 

        /// <summary>

        /// 셀 높이

        /// </summary>

        public int CellHeight { get; private set; }

 

        #endregion

        #region 전경 색상 인덱스 - ForegroundColorIndex

 

        /// <summary>

        /// 전경 색상 인덱스

        /// </summary>

        public int ForegroundColorIndex { get; private set; }

 

        #endregion

        #region 배경 색상 인덱스 - BackgroundColorIndex

 

        /// <summary>

        /// 배경 색상 인덱스

        /// </summary>

        public int BackgroundColorIndex { get; private set; }

 

        #endregion

        #region 텍스트 - Text

 

        /// <summary>

        /// 텍스트

        /// </summary>

        public string Text { get; private set; }

 

        #endregion

        #region GIF 확장 리스트 - ExtensionList

 

        /// <summary>

        /// GIF 확장 리스트

        /// </summary>

        public IList<GIFExtension> ExtensionList { get; private set; }

 

        #endregion

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.GraphicRendering;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFPlainTextExtension()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFPlainTextExtension()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 (비동기) - ReadAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>GIF 평문 텍스트 확장</returns>

        public new static async Task<GIFPlainTextExtension> ReadAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            GIFPlainTextExtension extension = new GIFPlainTextExtension();

 

            await extension.ReadInternalAsync(stream, extensionEnumerable).ConfigureAwait(false);

 

            return extension;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (내부용, 비동기) - ReadInternalAsync(stream, extensionEnumerable)

 

        /// <summary>

        /// 읽기 (내부용, 비동기)

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <param name="extensionEnumerable">GIF 확장 열거 가능형</param>

        /// <returns>태스크</returns>

        private async Task ReadInternalAsync(Stream stream, IEnumerable<GIFExtension> extensionEnumerable)

        {

            byte[] byteArray1 = new byte[13];

 

            await stream.ReadAllAsync(byteArray1, 0, byteArray1.Length).ConfigureAwait(false);

 

            BlockSize = byteArray1[0];

 

            if(BlockSize != 12)

            {

                throw GIFHelper.ThrowInvalidBlockSizeException("Plain Text Extension", 12, BlockSize);

            }

 

            Left                 = BitConverter.ToUInt16(byteArray1, 1);

            Top                  = BitConverter.ToUInt16(byteArray1, 3);

            Width                = BitConverter.ToUInt16(byteArray1, 5);

            Height               = BitConverter.ToUInt16(byteArray1, 7);

            CellWidth            = byteArray1[9];

            CellHeight           = byteArray1[10];

            ForegroundColorIndex = byteArray1[11];

            BackgroundColorIndex = byteArray1[12];

 

            byte[] byteArray2 = await GIFHelper.ReadDataBlocksAsync(stream).ConfigureAwait(false);

 

            Text = GIFHelper.GetString(byteArray2);

 

            ExtensionList = extensionEnumerable.ToList().AsReadOnly();

        }

 

        #endregion

    }

}

 

 

GIFTrailer.cs

 

 

using System.Threading.Tasks;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 트레일러

    /// </summary>

    public class GIFTrailer : GIFBlock

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 트레일러 바이트

        /// </summary>

        public const int TrailerByte = 0x3B;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region GIF 블럭 종류 - Kind

 

        /// <summary>

        /// GIF 블럭 종류

        /// </summary>

        public override GIFBlockKind Kind

        {

            get

            {

                return GIFBlockKind.Other;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 생성자 - GIFTrailer()

 

        /// <summary>

        /// 생성자

        /// </summary>

        private GIFTrailer()

        {

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 읽기 (비동기) - ReadAsync()

 

        /// <summary>

        /// 읽기 (비동기)

        /// </summary>

        /// <returns>GIF 트레일러 태스크</returns>

        public static Task<GIFTrailer> ReadAsync()

        {

            return Task.FromResult(new GIFTrailer());

        }

 

        #endregion

    }

}

 

 

IGIFRectangle.cs

 

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// GIF 사각형 인터페이스

    /// </summary>

    public interface IGIFRectangle

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

 

        #region 왼쪽 - Left

 

        /// <summary>

        /// 왼쪽

        /// </summary>

        int Left { get; }

 

        #endregion

        #region 위쪽 - Top

 

        /// <summary>

        /// 위쪽

        /// </summary>

        int Top { get; }

 

        #endregion

        #region 너비 - Width

 

        /// <summary>

        /// 너비

        /// </summary>

        int Width { get; }

 

        #endregion

        #region 높이 - Height

 

        /// <summary>

        /// 높이

        /// </summary>

        int Height { get; }

 

        #endregion

    }

}

 

 

InvalidBlockSizeException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// 무효 블럭 크기 예외

    /// </summary>

    [Serializable]

    public class InvalidBlockSizeException : GIFDecoderException

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - InvalidBlockSizeException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        public InvalidBlockSizeException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - InvalidBlockSizeException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        public InvalidBlockSizeException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - InvalidBlockSizeException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected InvalidBlockSizeException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

InvalidSignatureException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// 무효 서명 예외

    /// </summary>

    [Serializable]

    public class InvalidSignatureException : GIFDecoderException

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - InvalidSignatureException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        public InvalidSignatureException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - InvalidSignatureException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        public InvalidSignatureException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - InvalidSignatureException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected InvalidSignatureException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

UnknownBlockTypeException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// 알 수 없는 블럭 타입 예외

    /// </summary>

    [Serializable]

    public class UnknownBlockTypeException : GIFDecoderException

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - UnknownBlockTypeException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        public UnknownBlockTypeException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - UnknownBlockTypeException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        public UnknownBlockTypeException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - UnknownBlockTypeException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected UnknownBlockTypeException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

UnknownExtensionTypeException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// 알 수 없는 확장 타입 예외

    /// </summary>

    [Serializable]

    public class UnknownExtensionTypeException : GIFDecoderException

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - UnknownExtensionTypeException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        public UnknownExtensionTypeException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - UnknownExtensionTypeException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        public UnknownExtensionTypeException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - UnknownExtensionTypeException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected UnknownExtensionTypeException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

UnsupportedGIFVersionException.cs

 

 

using System;

using System.Runtime.Serialization;

 

namespace TestLibrary.Decoding

{

    /// <summary>

    /// 미지원 GIF 버전 예외

    /// </summary>

    [Serializable]

    public class UnsupportedGIFVersionException : GIFDecoderException

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - UnsupportedGIFVersionException(message)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        public UnsupportedGIFVersionException(string message) : base(message)

        {

        }

 

        #endregion

        #region 생성자 - UnsupportedGIFVersionException(message, innerException)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="message">메시지</param>

        /// <param name="innerException">내부 예외</param>

        public UnsupportedGIFVersionException(string message, Exception innerException) : base(message, innerException)

        {

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - UnsupportedGIFVersionException(info, context)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="info">직렬화 정보</param>

        /// <param name="context">스트리밍 컨텍스트</param>

        protected UnsupportedGIFVersionException(SerializationInfo info, StreamingContext context) : base(info, context)

        {

        }

 

        #endregion

    }

}

 

 

BitReader.cs

 

 

namespace TestLibrary.Decompression

{

    /// <summary>

    /// 비트 리더

    /// </summary>

    internal class BitReader

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 바이트 배열

        /// </summary>

        private readonly byte[] byteArray;

 

        /// <summary>

        /// 바이트 위치

        /// </summary>

        private int bytePosition = -1;

 

        /// <summary>

        /// 비트 위치

        /// </summary>

        private int bitPosition;

 

        /// <summary>

        /// 현재 값

        /// </summary>

        private int currentValue = -1;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - BitReader(byteArray)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="byteArray">바이트 배열</param>

        public BitReader(byte[] byteArray)

        {

            this.byteArray = byteArray;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 비트 읽기 - ReadBits(bitCount)

 

        /// <summary>

        /// 비트 읽기

        /// </summary>

        /// <param name="bitCount">비트 카운트</param>

        /// <returns>비트</returns>

        public int ReadBits(int bitCount)

        {

            if(this.bytePosition == -1)

            {

                this.bytePosition = 0;

                this.bitPosition  = 0;

                this.currentValue = ReadInteger();

            }

            else if(bitCount > 32 - this.bitPosition)

            {

                int temporaryValue = this.bitPosition >> 3;

 

                this.bytePosition += temporaryValue;

 

                this.bitPosition &= 0x07;

 

                this.currentValue = ReadInteger() >> this.bitPosition;

            }

 

            int mask  = (1 << bitCount) - 1;

            int value = this.currentValue & mask;

 

            this.currentValue >>= bitCount;

 

            this.bitPosition += bitCount;

 

            return value;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 정수 읽기 - ReadInteger()

 

        /// <summary>

        /// 정수 읽기

        /// </summary>

        /// <returns>정수</returns>

        private int ReadInteger()

        {

            int value = 0;

 

            for(int i = 0; i < 4; i++)

            {

                if(this.bytePosition + i >= this.byteArray.Length)

                {

                    break;

                }

 

                value |= this.byteArray[this.bytePosition + i] << (i << 3);

            }

 

            return value;

        }

 

        #endregion

    }

}

 

 

LZWDecompressStream.cs

 

 

using System;

using System.Diagnostics;

using System.IO;

using System.Runtime.CompilerServices;

 

using Buffer = System.Buffer;

 

namespace TestLibrary.Decompression

{

    /// <summary>

    /// LZW 압축 해제 스트림

    /// </summary>

    public class LZWDecompressStream : Stream

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Structure

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

 

        #region 시퀀스 - Sequence

 

        /// <summary>

        /// 시퀀스

        /// </summary>

        internal struct Sequence

        {

            //////////////////////////////////////////////////////////////////////////////////////////////////// Property

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 바이트 배열 - ByteArray

 

            /// <summary>

            /// 바이트 배열

            /// </summary>

            public byte[] ByteArray { get; }

 

            #endregion

            #region 클리어 코드 - IsClearCode

 

            /// <summary>

            /// 클리어 코드

            /// </summary>

            public bool IsClearCode { get; }

 

            #endregion

            #region 중단 코드 여부 - IsStopCode

 

            /// <summary>

            /// 중단 코드 여부

            /// </summary>

            public bool IsStopCode { get; }

 

            #endregion

            #region 클리어 코드 - ClearCode

 

            /// <summary>

            /// 클리어 코드

            /// </summary>

            public static Sequence ClearCode { get; } = new Sequence(true, false);

 

            #endregion

            #region 중단 코드 - StopCode

 

            /// <summary>

            /// 중단 코드

            /// </summary>

            public static Sequence StopCode { get; } = new Sequence(false, true);

 

            #endregion

 

            //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 생성자 - Sequence(sourceByteArray)

 

            /// <summary>

            /// 생성자

            /// </summary>

            /// <param name="sourceByteArray"></param>

            public Sequence(byte[] sourceByteArray) : this()

            {

                ByteArray = sourceByteArray;

            }

 

            #endregion

 

            ////////////////////////////////////////////////////////////////////////////////////////// Private

 

            #region 생성자 - Sequence(isClearCode, isStopCode)

 

            /// <summary>

            /// 생성자

            /// </summary>

            /// <param name="isClearCode">클리어 코드 여부</param>

            /// <param name="isStopCode">중단 코드 여부</param>

            private Sequence(bool isClearCode, bool isStopCode) : this()

            {

                IsClearCode = isClearCode;

                IsStopCode  = isStopCode;

            }

 

            #endregion

 

            //////////////////////////////////////////////////////////////////////////////////////////////////// Method

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 추가하기 - Append(value)

 

            /// <summary>

            /// 추가하기

            /// </summary>

            /// <param name="value"></param>

            /// <returns>시퀀스</returns>

            public Sequence Append(byte value)

            {

                byte[] byteArray = new byte[ByteArray.Length + 1];

 

                ByteArray.CopyTo(byteArray, 0);

 

                byteArray[ByteArray.Length] = value;

 

                return new Sequence(byteArray);

            }

 

            #endregion

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Class

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

 

        #region 색상 테이블 - CodeTable

 

        /// <summary>

        /// 색상 테이블

        /// </summary>

        internal class CodeTable

        {

            //////////////////////////////////////////////////////////////////////////////////////////////////// Field

            ////////////////////////////////////////////////////////////////////////////////////////// Private

 

            #region Field

 

            /// <summary>

            /// 최소 코드 길이

            /// </summary>

            private readonly int minimumCodeLength;

 

            /// <summary>

            /// 시퀀스 배열

            /// </summary>

            private readonly Sequence[] sequenceArray;

 

            /// <summary>

            /// 카운트

            /// </summary>

            private int count;

 

            /// <summary>

            /// 코드 길이

            /// </summary>

            private int codeLength;

 

            #endregion

 

            //////////////////////////////////////////////////////////////////////////////////////////////////// Property

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 인덱서 - this[index]

 

            /// <summary>

            /// 인덱서

            /// </summary>

            /// <param name="index">인덱스</param>

            /// <returns>시퀀스</returns>

            public Sequence this[int index]

            {

                [MethodImpl(MethodImplOptions.AggressiveInlining)]

                get

                {

                    return this.sequenceArray[index];

                }

            }

 

            #endregion

            #region 카운트 - Count

 

            /// <summary>

            /// 카운트

            /// </summary>

            public int Count

            {

                [MethodImpl(MethodImplOptions.AggressiveInlining)]

                get

                {

                    return this.count;

                }

            }

 

            #endregion

            #region 코드 길이 - CodeLength

 

            /// <summary>

            /// 코드 길이

            /// </summary>

            public int CodeLength

            {

                [MethodImpl(MethodImplOptions.AggressiveInlining)]

                get

                {

                    return this.codeLength;

                }

            }

 

            #endregion

 

            //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 생성자 - CodeTable(minimumCodeLength)

 

            /// <summary>

            /// 생성자

            /// </summary>

            /// <param name="minimumCodeLength">최소 코드 길이</param>

            public CodeTable(int minimumCodeLength)

            {

                this.minimumCodeLength = minimumCodeLength;

                this.codeLength        = this.minimumCodeLength + 1;

 

                int initialEntryCount = 1 << minimumCodeLength;

 

                this.sequenceArray = new Sequence[1 << MaximumCodeLength];

 

                for(int i = 0; i < initialEntryCount; i++)

                {

                    this.sequenceArray[this.count++] = new Sequence(new[] {(byte) i});

                }

 

                Add(Sequence.ClearCode);

                Add(Sequence.StopCode );

            }

 

            #endregion

 

            //////////////////////////////////////////////////////////////////////////////////////////////////// Method

            ////////////////////////////////////////////////////////////////////////////////////////// Public

 

            #region 리셋하기 - Reset()

 

            /// <summary>

            /// 리셋하기

            /// </summary>

            [MethodImpl(MethodImplOptions.AggressiveInlining)]

            public void Reset()

            {

                this.count = (1 << this.minimumCodeLength) + 2;

 

                this.codeLength = this.minimumCodeLength + 1;

            }

 

            #endregion

            #region 추가하기 - Add(sequence)

 

            /// <summary>

            /// 추가하기

            /// </summary>

            /// <param name="sequence">시퀀스</param>

            [MethodImpl(MethodImplOptions.AggressiveInlining)]

            public void Add(Sequence sequence)

            {

                if(this.count >= this.sequenceArray.Length)

                {

                    return;

                }

 

                this.sequenceArray[this.count++] = sequence;

 

                if((this.count & (this.count - 1)) == 0 && this.codeLength < MaximumCodeLength)

                {

                    this.codeLength++;

                }

            }

 

            #endregion

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 최대 코드 길이

        /// </summary>

        private const int MaximumCodeLength = 12;

 

        /// <summary>

        /// 비트 리더

        /// </summary>

        private readonly BitReader reader;

 

        /// <summary>

        /// 코드 테이블

        /// </summary>

        private readonly CodeTable codeTable;

 

        /// <summary>

        /// 이전 코드

        /// </summary>

        private int previousCode;

 

        /// <summary>

        /// 잔여 바이트 배열

        /// </summary>

        private byte[] remainingByteArray;

 

        /// <summary>

        /// 스트림 끝 여부

        /// </summary>

        private bool endOfStream;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 읽기 가능 여부 - CanRead

 

        /// <summary>

        /// 읽기 가능 여부

        /// </summary>

        public override bool CanRead => true;

 

        #endregion

        #region 탐색 가능 여부 - CanSeek

 

        /// <summary>

        /// 탐색 가능 여부

        /// </summary>

        public override bool CanSeek => false;

 

        #endregion

        #region 쓰기 가능 여부 - CanWrite

 

        /// <summary>

        /// 쓰기 가능 여부

        /// </summary>

        public override bool CanWrite => true;

 

        #endregion

        #region 길이 - Length

 

        /// <summary>

        /// 길이

        /// </summary>

        public override long Length

        {

            get

            {

                throw new NotSupportedException();

            }

        }

 

        #endregion

        #region 위치 - Position

 

        /// <summary>

        /// 위치

        /// </summary>

        public override long Position

        {

            get

            {

                throw new NotSupportedException();

            }

            set

            {

                throw new NotSupportedException();

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - LZWDecompressStream(compressedByteArray, minimumCodeLength)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="compressedByteArray">압축 바이트 배열</param>

        /// <param name="minimumCodeLength">최소 코드 길이</param>

        public LZWDecompressStream(byte[] compressedByteArray, int minimumCodeLength)

        {

            this.reader    = new BitReader(compressedByteArray);

            this.codeTable = new CodeTable(minimumCodeLength);

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 검증하기 - Validate(sourceByteArray, offset, count)

 

        /// <summary>

        /// 검증하기

        /// </summary>

        /// <param name="sourceByteArray">소스 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        [Conditional("DISABLED")]

        private static void Validate(byte[] sourceByteArray, int offset, int count)

        {

            if(sourceByteArray == null)

            {

                throw new ArgumentNullException(nameof(sourceByteArray));

            }

 

            if(offset < 0)

            {

                throw new ArgumentOutOfRangeException(nameof(offset), "Offset can't be negative");

            }

 

            if(count < 0)

            {

                throw new ArgumentOutOfRangeException(nameof(count), "Count can't be negative");

            }

 

            if(offset + count > sourceByteArray.Length)

            {

                throw new ArgumentException("Buffer is to small to receive the requested data");

            }

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 버퍼 비우기 - Flush()

 

        /// <summary>

        /// 버퍼 비우기

        /// </summary>

        public override void Flush()

        {

        }

 

        #endregion

        #region 탐색하기 - Seek(offset, origin)

 

        /// <summary>

        /// 탐색하기

        /// </summary>

        /// <param name="offset">오프셋</param>

        /// <param name="origin">탐색 기준</param>

        /// <returns>위치</returns>

        public override long Seek(long offset, SeekOrigin origin)

        {

            throw new NotSupportedException();

        }

 

        #endregion

        #region 길이 설정하기 - SetLength(value)

 

        /// <summary>

        /// 길이 설정하기

        /// </summary>

        /// <param name="value"></param>

        public override void SetLength(long value)

        {

            throw new NotSupportedException();

        }

 

        #endregion

        #region 읽기 - Read(targetByteArray, offset, count)

 

        /// <summary>

        /// 읽기

        /// </summary>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        /// <returns></returns>

        public override int Read(byte[] targetByteArray, int offset, int count)

        {

            Validate(targetByteArray, offset, count);

 

            if(this.endOfStream)

            {

                return 0;

            }

 

            int readCount = 0;

 

            FlushRemainingByteArray(targetByteArray, offset, count, ref readCount);

 

            while(readCount < count)

            {

                int code = this.reader.ReadBits(this.codeTable.CodeLength);

                

                if(!ProcessCode(code, targetByteArray, offset, count, ref readCount))

                {

                    this.endOfStream = true;

 

                    break;

                }

            }

 

            return readCount;

        }

 

        #endregion

        #region 쓰기 - Write(sourceByteArray, offset, count)

 

        /// <summary>

        /// 쓰기

        /// </summary>

        /// <param name="sourceByteArray">소스 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        public override void Write(byte[] sourceByteArray, int offset, int count)

        {

            throw new NotSupportedException();

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 바이트 배열 복사하기 - CopyByteArray(sourceByteArray, targetByteArray, offset, count, readCount)

 

        /// <summary>

        /// 바이트 배열 복사하기

        /// </summary>

        /// <param name="sourceByteArray">소스 바이트 배열</param>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        /// <param name="readCount">읽기 카운트</param>

        /// <returns>바이트 배열</returns>

        private static byte[] CopyByteArray(byte[] sourceByteArray, byte[] targetByteArray, int offset, int count, ref int readCount)

        {

            int byteCountToRead = Math.Min(sourceByteArray.Length, count - readCount);

 

            Buffer.BlockCopy(sourceByteArray, 0, targetByteArray, offset + readCount, byteCountToRead);

 

            readCount += byteCountToRead;

 

            byte[] remainingByteArray = null;

 

            if(byteCountToRead < sourceByteArray.Length)

            {

                int remainingBytesCount = sourceByteArray.Length - byteCountToRead;

 

                remainingByteArray = new byte[remainingBytesCount];

 

                Buffer.BlockCopy(sourceByteArray, byteCountToRead, remainingByteArray, 0, remainingBytesCount);

            }

 

            return remainingByteArray;

        }

 

        #endregion

        #region 잔여 바이트 배열 비우기 - FlushRemainingByteArray(targetByteArray, offset, count, readCount)

 

        /// <summary>

        /// 잔여 바이트 배열 비우기

        /// </summary>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        /// <param name="readCount">읽기 카운트</param>

        private void FlushRemainingByteArray(byte[] targetByteArray, int offset, int count, ref int readCount)

        {

            if(this.remainingByteArray != null)

            {

                this.remainingByteArray = CopyByteArray(this.remainingByteArray, targetByteArray, offset, count, ref readCount);

            }

        }

 

        #endregion

        #region 코드 테이블 초기화하기 - InitializeCodeTable()

 

        /// <summary>

        /// 코드 테이블 초기화하기

        /// </summary>

        private void InitializeCodeTable()

        {

            this.codeTable.Reset();

 

            this.previousCode = -1;

        }

 

        #endregion

        #region 코드 처리하기 - ProcessCode(code, targetByteArray, offset, count, readCount)

 

        /// <summary>

        /// 코드 처리하기

        /// </summary>

        /// <param name="code">코드</param>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        /// <param name="readCount">읽기 카운트</param>

        /// <returns>처리 결과</returns>

        private bool ProcessCode(int code, byte[] targetByteArray, int offset, int count, ref int readCount)

        {

            if(code < this.codeTable.Count)

            {

                LZWDecompressStream.Sequence sequence = this.codeTable[code];

 

                if(sequence.IsStopCode)

                {

                    return false;

                }

 

                if(sequence.IsClearCode)

                {

                    InitializeCodeTable();

 

                    return true;

                }

 

                this.remainingByteArray = CopyByteArray(sequence.ByteArray, targetByteArray, offset, count, ref readCount);

 

                if(this.previousCode >= 0)

                {

                    LZWDecompressStream.Sequence previousSequence = this.codeTable[this.previousCode];

 

                    LZWDecompressStream.Sequence newSequence = previousSequence.Append(sequence.ByteArray[0]);

 

                    this.codeTable.Add(newSequence);

                }

            }

            else

            {

                LZWDecompressStream.Sequence previousSequence = this.codeTable[this.previousCode];

 

                LZWDecompressStream.Sequence newSequence = previousSequence.Append(previousSequence.ByteArray[0]);

 

                this.codeTable.Add(newSequence);

 

                this.remainingByteArray = CopyByteArray(newSequence.ByteArray, targetByteArray, offset, count, ref readCount);

            }

 

            this.previousCode = code;

 

            return true;

        }

 

        #endregion

    }

}

 

 

BitArrayExtension.cs

 

 

using System.Collections;

 

namespace TestLibrary.Extension

{

    /// <summary>

    /// 비트 배열 확장

    /// </summary>

    public static class BitArrayExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 16비트 정수 구하기 - ToInt16(bitArray)

 

        /// <summary>

        /// 16비트 정수 구하기

        /// </summary>

        /// <param name="bitArray">비트 배열</param>

        /// <returns>16비트 정수</returns>

        public static short ToInt16(this BitArray bitArray)

        {

            short value = 0;

 

            for(int i = bitArray.Length - 1; i >= 0; i--)

            {

                value = (short)((value << 1) + (bitArray[i] ? 1 : 0));

            }

 

            return value;

        }

 

        #endregion

    }

}

 

 

StreamExtension.cs

 

 

using System;

using System.IO;

using System.Threading;

using System.Threading.Tasks;

 

namespace TestLibrary.Extension

{

    /// <summary>

    /// 스트림 확장

    /// </summary>

    public static class StreamExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 모두 읽기 (비동기) - ReadAllAsync(sourceStream, targetByteArray, offset, count, cancellationToken)

 

        /// <summary>

        /// 모두 읽기 (비동기)

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>태스크</returns>

        public static async Task ReadAllAsync

        (

            this Stream       sourceStream,

            byte[]            targetByteArray,

            int               offset,

            int               count,

            CancellationToken cancellationToken = default

        )

        {

            int totalReadCount = 0;

 

            while(totalReadCount < count)

            {

#if LACKS_STREAM_MEMORY_OVERLOADS

 

                int readCount = await sourceStream.ReadAsync

                (

                    targetByteArray,

                    offset + totalReadCount,

                    count - totalReadCount,

                    cancellationToken

                );

 

#else

 

                int readCount = await sourceStream.ReadAsync

                (

                    targetByteArray.AsMemory(offset + totalReadCount, count - totalReadCount),

                    cancellationToken

                );

 

#endif

 

                if(readCount == 0)

                {

                    throw new EndOfStreamException();

                }

 

                totalReadCount += readCount;

            }

        }

 

        #endregion

        #region 모두 읽기 - ReadAll(sourceStream, targetByteArray, offset, count)

 

        /// <summary>

        /// 모두 읽기

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="targetByteArray">타겟 바이트 배열</param>

        /// <param name="offset">오프셋</param>

        /// <param name="count">카운트</param>

        public static void ReadAll(this Stream sourceStream, byte[] targetByteArray, int offset, int count)

        {

            int totalReadCount = 0;

 

            while(totalReadCount < count)

            {

                int readCount = sourceStream.Read(targetByteArray, offset + totalReadCount, count - totalReadCount);

 

                if(readCount == 0)

                {

                    throw new EndOfStreamException();

                }

 

                totalReadCount += readCount;

            }

        }

 

        #endregion

        #region 바이트 읽기 (비동기) - ReadByteAsync(sourceStream, cancellationToken)

 

        /// <summary>

        /// 바이트 읽기 (비동기)

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>정수 태스크</returns>

        public static async Task<int> ReadByteAsync(this Stream sourceStream, CancellationToken cancellationToken = default)

        {

            byte[] targetByteArray = new byte[1];

 

#if LACKS_STREAM_MEMORY_OVERLOADS

 

            int readCount = await sourceStream.ReadAsync(targetByteArray, 0, 1, cancellationToken);

#else

 

            int readCount = await sourceStream.ReadAsync(targetByteArray.AsMemory(0, 1), cancellationToken);

 

#endif

 

            if(readCount == 0)

            {

                return -1;

            }

 

            return targetByteArray[0];

        }

 

        #endregion

        #region 버퍼 스트림 구하기 - AsBuffered(stream)

 

        /// <summary>

        /// 버퍼 스트림 구하기

        /// </summary>

        /// <param name="stream">스트림</param>

        /// <returns>버퍼 스트림</returns>

        public static Stream AsBuffered(this Stream stream)

        {

            if(stream is BufferedStream bufferedStream)

            {

                return bufferedStream;

            }

 

            return new BufferedStream(stream);

        }

 

        #endregion

        #region 복사하기 (비동기) - CopyToAsync(sourceStream, targetStream, progress, bufferSize, cancellationToken)

 

        /// <summary>

        /// 복사하기 (비동기)

        /// </summary>

        /// <param name="sourceStream">소스 스트림</param>

        /// <param name="targetStream">타겟 스트림</param>

        /// <param name="progress">진행 인터페이스</param>

        /// <param name="bufferSize">버퍼 크기</param>

        /// <param name="cancellationToken">취소 토큰</param>

        /// <returns>태스크</returns>

        public static async Task CopyToAsync

        (

            this Stream       sourceStream,

            Stream            targetStream,

            IProgress<long>   progress,

            int               bufferSize = 81920,

            CancellationToken cancellationToken = default

        )

        {

            byte[] byteArray       = new byte[bufferSize];

            int    byteCountRead;

            long   byteCountCopied = 0;

 

#if LACKS_STREAM_MEMORY_OVERLOADS

 

            while((byteCountRead = await sourceStream.ReadAsync

            (

                byteArray,

                0,

                       byteArray.Length,

                       cancellationToken

                 ).ConfigureAwait(false)) != 0)

#else

            while((byteCountRead = await sourceStream.ReadAsync

                 (

                     byteArray.AsMemory(),

                     cancellationToken

                 ).ConfigureAwait(false)) != 0)

#endif

            {

#if LACKS_STREAM_MEMORY_OVERLOADS

 

                await targetStream.WriteAsync(byteArray, 0, byteCountRead, cancellationToken).ConfigureAwait(false);

 

#else

 

                await targetStream.WriteAsync(byteArray.AsMemory(0, byteCountRead), cancellationToken).ConfigureAwait(false);

 

#endif

 

                byteCountCopied += byteCountRead;

 

                progress?.Report(byteCountCopied);

            }

        }

 

        #endregion

    }

}

 

 

WritableBitmapExtension.cs

 

 

using System;

using System.Windows.Media.Imaging;

 

namespace TestLibrary.Extension

{

    /// <summary>

    /// 쓰기 가능 비트맵 확장

    /// </summary>

    public static class WritableBitmapExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Class