// 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) { }
}
}