OSDN Git Service

ブロックの括弧を独立した行に書く (SA1500, SA1501, SA1502)
[opentween/open-tween.git] / OpenTween / MemoryImage.cs
index 9bb0760..cc1150b 100644 (file)
 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 // Boston, MA 02110-1301, USA.
 
+#nullable enable
+
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.Serialization;
 using System.Text;
-using System.Drawing;
-using System.IO;
 using System.Threading.Tasks;
 
 namespace OpenTween
@@ -38,21 +42,18 @@ namespace OpenTween
     /// Image.FromStream() を使用して Image を生成する場合、
     /// Image を破棄するまでの間は元となった Stream を破棄できないためその対策として使用する。
     /// </remarks>
-    public class MemoryImage : ICloneable, IDisposable
+    public class MemoryImage : ICloneable, IDisposable, IEquatable<MemoryImage>
     {
-        public readonly Stream Stream;
-        public readonly Image Image;
-
-        protected bool disposed = false;
+        private readonly Image image;
 
         /// <exception cref="InvalidImageException">
         /// ストリームから読みだされる画像データが不正な場合にスローされる
         /// </exception>
-        protected MemoryImage(Stream stream)
+        protected MemoryImage(MemoryStream stream)
         {
             try
             {
-                this.Image = Image.FromStream(stream);
+                this.image = Image.FromStream(stream);
             }
             catch (ArgumentException e)
             {
@@ -80,19 +81,145 @@ namespace OpenTween
             this.Stream = stream;
         }
 
+        /// <summary>
+        /// MemoryImage が保持している画像
+        /// </summary>
+        public Image Image
+        {
+            get
+            {
+                if (this.IsDisposed)
+                    throw new ObjectDisposedException("this");
+
+                return this.image;
+            }
+        }
+
+        /// <summary>
+        /// MemoryImage が保持している画像のストリーム
+        /// </summary>
+        public MemoryStream Stream { get; }
+
+        /// <summary>
+        /// MemoryImage が破棄されているか否か
+        /// </summary>
+        public bool IsDisposed { get; private set; } = false;
+
+        /// <summary>
+        /// MemoryImage が保持している画像のフォーマット
+        /// </summary>
+        public ImageFormat ImageFormat
+            => this.Image.RawFormat;
+
+        /// <summary>
+        /// MemoryImage が保持している画像のフォーマットに相当する拡張子 (ピリオド付き)
+        /// </summary>
+        public string ImageFormatExt
+        {
+            get
+            {
+                var format = this.ImageFormat;
+
+                // ImageFormat は == で正しく比較できないため Equals を使用する必要がある
+                if (format.Equals(ImageFormat.Bmp))
+                    return ".bmp";
+                if (format.Equals(ImageFormat.Emf))
+                    return ".emf";
+                if (format.Equals(ImageFormat.Gif))
+                    return ".gif";
+                if (format.Equals(ImageFormat.Icon))
+                    return ".ico";
+                if (format.Equals(ImageFormat.Jpeg))
+                    return ".jpg";
+                if (format.Equals(ImageFormat.MemoryBmp))
+                    return ".bmp";
+                if (format.Equals(ImageFormat.Png))
+                    return ".png";
+                if (format.Equals(ImageFormat.Tiff))
+                    return ".tiff";
+                if (format.Equals(ImageFormat.Wmf))
+                    return ".wmf";
+
+                // 対応する形式がなければ空文字列を返す
+                // (上記以外のフォーマットは Image.FromStream を通過できないため、ここが実行されることはまず無い)
+                return string.Empty;
+            }
+        }
+
+        /// <summary>
+        /// MemoryImage インスタンスを複製します
+        /// </summary>
+        /// <remarks>
+        /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
+        /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
+        /// </remarks>
+        /// <returns>複製された MemoryImage</returns>
         public MemoryImage Clone()
         {
+            this.Stream.Seek(0, SeekOrigin.Begin);
+
             return MemoryImage.CopyFromStream(this.Stream);
         }
 
-        object ICloneable.Clone()
+        /// <summary>
+        /// MemoryImage インスタンスを非同期に複製します
+        /// </summary>
+        /// <remarks>
+        /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
+        /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
+        /// </remarks>
+        /// <returns>複製された MemoryImage を返すタスク</returns>
+        public Task<MemoryImage> CloneAsync()
+        {
+            this.Stream.Seek(0, SeekOrigin.Begin);
+
+            return MemoryImage.CopyFromStreamAsync(this.Stream);
+        }
+
+        public override int GetHashCode()
         {
-            return this.Clone();
+            using var sha1service = new System.Security.Cryptography.SHA1CryptoServiceProvider();
+            var hash = sha1service.ComputeHash(this.Stream.GetBuffer(), 0, (int)this.Stream.Length);
+            return Convert.ToBase64String(hash).GetHashCode();
         }
 
+        public override bool Equals(object? other)
+            => this.Equals(other as MemoryImage);
+
+        public bool Equals(MemoryImage? other)
+        {
+            if (object.ReferenceEquals(this, other))
+                return true;
+
+            if (other == null)
+                return false;
+
+            // それぞれが保持する MemoryStream の内容が等しいことを検証する
+
+            var selfLength = this.Stream.Length;
+            var otherLength = other.Stream.Length;
+
+            if (selfLength != otherLength)
+                return false;
+
+            var selfBuffer = this.Stream.GetBuffer();
+            var otherBuffer = other.Stream.GetBuffer();
+
+            for (var pos = 0L; pos < selfLength; pos++)
+            {
+                if (selfBuffer[pos] != otherBuffer[pos])
+                    return false;
+            }
+
+            return true;
+        }
+
+        object ICloneable.Clone()
+            => this.Clone();
+
         protected virtual void Dispose(bool disposing)
         {
-            if (this.disposed) return;
+            if (this.IsDisposed) return;
 
             if (disposing)
             {
@@ -100,7 +227,7 @@ namespace OpenTween
                 this.Stream.Dispose();
             }
 
-            this.disposed = true;
+            this.IsDisposed = true;
         }
 
         public void Dispose()
@@ -112,9 +239,7 @@ namespace OpenTween
         }
 
         ~MemoryImage()
-        {
-            this.Dispose(false);
-        }
+            => this.Dispose(false);
 
         /// <summary>
         /// 指定された Stream から MemoryImage を作成します。
@@ -128,12 +253,48 @@ namespace OpenTween
         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
         public static MemoryImage CopyFromStream(Stream stream)
         {
-            var memstream = new MemoryStream();
+            MemoryStream? memstream = null;
+            try
+            {
+                memstream = new MemoryStream();
+
+                stream.CopyTo(memstream);
+
+                return new MemoryImage(memstream);
+            }
+            catch
+            {
+                memstream?.Dispose();
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 指定された Stream から MemoryImage を非同期に作成します。
+        /// </summary>
+        /// <remarks>
+        /// ストリームの内容はメモリ上に展開した後に使用されるため、
+        /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
+        /// </remarks>
+        /// <param name="stream">読み込む対象となる Stream</param>
+        /// <returns>作成された MemoryImage を返すタスク</returns>
+        /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
+        public static async Task<MemoryImage> CopyFromStreamAsync(Stream stream)
+        {
+            MemoryStream? memstream = null;
+            try
+            {
+                memstream = new MemoryStream();
 
-            stream.CopyTo(memstream);
-            memstream.Seek(0, SeekOrigin.Begin);
+                await stream.CopyToAsync(memstream).ConfigureAwait(false);
 
-            return new MemoryImage(memstream);
+                return new MemoryImage(memstream);
+            }
+            catch
+            {
+                memstream?.Dispose();
+                throw;
+            }
         }
 
         /// <summary>
@@ -144,18 +305,69 @@ namespace OpenTween
         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
         public static MemoryImage CopyFromBytes(byte[] bytes)
         {
-            return new MemoryImage(new MemoryStream(bytes));
+            MemoryStream? memstream = null;
+            try
+            {
+                memstream = new MemoryStream(bytes);
+                return new MemoryImage(memstream);
+            }
+            catch
+            {
+                memstream?.Dispose();
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// Image インスタンスから MemoryImage を作成します
+        /// </summary>
+        /// <remarks>
+        /// PNG 画像として描画し直す処理を含むため、極力 Stream や byte[] を渡す他のメソッドを使用すべきです
+        /// </remarks>
+        /// <param name="image">対象となる画像</param>
+        /// <returns>作成された MemoryImage</returns>
+        public static MemoryImage CopyFromImage(Image image)
+        {
+            MemoryStream? memstream = null;
+            try
+            {
+                memstream = new MemoryStream();
+
+                image.Save(memstream, ImageFormat.Png);
+
+                return new MemoryImage(memstream);
+            }
+            catch
+            {
+                memstream?.Dispose();
+                throw;
+            }
         }
     }
 
     /// <summary>
     /// 不正な画像データに対してスローされる例外
     /// </summary>
+    [Serializable]
     public class InvalidImageException : Exception
     {
-        public InvalidImageException() : base() { }
-        public InvalidImageException(string message) : base(message) { }
-        public InvalidImageException(string message, Exception innerException) : base(message, innerException) { }
-        public InvalidImageException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+        public InvalidImageException()
+        {
+        }
+
+        public InvalidImageException(string message)
+            : base(message)
+        {
+        }
+
+        public InvalidImageException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
+
+        protected InvalidImageException(SerializationInfo info, StreamingContext context)
+            : base(info, context)
+        {
+        }
     }
 }