// OpenTween - Client of Twitter // Copyright (c) 2013 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; using System.Threading.Tasks; using System.Drawing.Imaging; namespace OpenTween { /// /// Image と Stream を対に保持するためのクラス /// /// /// Image.FromStream() を使用して Image を生成する場合、 /// Image を破棄するまでの間は元となった Stream を破棄できないためその対策として使用する。 /// public class MemoryImage : ICloneable, IDisposable, IEquatable { private readonly Image image; /// /// ストリームから読みだされる画像データが不正な場合にスローされる /// protected MemoryImage(MemoryStream stream) { try { this.image = Image.FromStream(stream); } catch (ArgumentException e) { stream.Dispose(); throw new InvalidImageException("Invalid image", e); } catch (OutOfMemoryException e) { // GDI+ がサポートしない画像形式で OutOfMemoryException がスローされる場合があるらしい stream.Dispose(); throw new InvalidImageException("Invalid image?", e); } catch (ExternalException e) { // 「GDI+ で汎用エラーが発生しました」という大雑把な例外がスローされる場合があるらしい stream.Dispose(); throw new InvalidImageException("Invalid image?", e); } catch (Exception) { stream.Dispose(); throw; } this.Stream = stream; } /// /// MemoryImage が保持している画像 /// public Image Image { get { if (this.IsDisposed) throw new ObjectDisposedException("this"); return this.image; } } /// /// MemoryImage が保持している画像のストリーム /// public MemoryStream Stream { get; } /// /// MemoryImage が破棄されているか否か /// public bool IsDisposed { get; private set; } = false; /// /// MemoryImage が保持している画像のフォーマット /// public ImageFormat ImageFormat => this.Image.RawFormat; /// /// MemoryImage が保持している画像のフォーマットに相当する拡張子 (ピリオド付き) /// 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; } } /// /// MemoryImage インスタンスを複製します /// /// /// メソッド実行中にストリームのシークが行われないよう注意して下さい。 /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。 /// /// 複製された MemoryImage public MemoryImage Clone() { this.Stream.Seek(0, SeekOrigin.Begin); return MemoryImage.CopyFromStream(this.Stream); } /// /// MemoryImage インスタンスを非同期に複製します /// /// /// メソッド実行中にストリームのシークが行われないよう注意して下さい。 /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。 /// /// 複製された MemoryImage を返すタスク public Task CloneAsync() { this.Stream.Seek(0, SeekOrigin.Begin); return MemoryImage.CopyFromStreamAsync(this.Stream); } public override int GetHashCode() { 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.IsDisposed) return; if (disposing) { this.Image.Dispose(); this.Stream.Dispose(); } this.IsDisposed = true; } public void Dispose() { this.Dispose(true); // 明示的にDisposeが呼ばれた場合はファイナライザを使用しない GC.SuppressFinalize(this); } ~MemoryImage() => this.Dispose(false); /// /// 指定された Stream から MemoryImage を作成します。 /// /// /// ストリームの内容はメモリ上に展開した後に使用されるため、 /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。 /// /// 読み込む対象となる Stream /// 作成された MemoryImage /// 不正な画像データが入力された場合 public static MemoryImage CopyFromStream(Stream stream) { MemoryStream memstream = null; try { memstream = new MemoryStream(); stream.CopyTo(memstream); return new MemoryImage(memstream); } catch { memstream?.Dispose(); throw; } } /// /// 指定された Stream から MemoryImage を非同期に作成します。 /// /// /// ストリームの内容はメモリ上に展開した後に使用されるため、 /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。 /// /// 読み込む対象となる Stream /// 作成された MemoryImage を返すタスク /// 不正な画像データが入力された場合 public async static Task CopyFromStreamAsync(Stream stream) { MemoryStream memstream = null; try { memstream = new MemoryStream(); await stream.CopyToAsync(memstream).ConfigureAwait(false); return new MemoryImage(memstream); } catch { memstream?.Dispose(); throw; } } /// /// 指定されたバイト列から MemoryImage を作成します。 /// /// 読み込む対象となるバイト列 /// 作成された MemoryImage /// 不正な画像データが入力された場合 public static MemoryImage CopyFromBytes(byte[] bytes) { MemoryStream memstream = null; try { memstream = new MemoryStream(bytes); return new MemoryImage(memstream); } catch { memstream?.Dispose(); throw; } } /// /// Image インスタンスから MemoryImage を作成します /// /// /// PNG 画像として描画し直す処理を含むため、極力 Stream や byte[] を渡す他のメソッドを使用すべきです /// /// 対象となる画像 /// 作成された MemoryImage 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; } } } /// /// 不正な画像データに対してスローされる例外 /// [Serializable] public class InvalidImageException : Exception { 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) { } } }