// 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
/// 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)
{
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)
{
this.Stream.Dispose();
}
- this.disposed = true;
+ this.IsDisposed = true;
}
public void Dispose()
}
~MemoryImage()
- {
- this.Dispose(false);
- }
+ => this.Dispose(false);
/// <summary>
/// 指定された Stream から MemoryImage を作成します。
/// <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>
/// <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)
+ {
+ }
}
}