OSDN Git Service

d7f3f09193e2256fe174784382b58bcd77eb62ee
[opentween/open-tween.git] / OpenTween / MemoryImage.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2013 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Collections.Generic;
24 using System.Linq;
25 using System.Runtime.InteropServices;
26 using System.Runtime.Serialization;
27 using System.Text;
28 using System.Diagnostics.CodeAnalysis;
29 using System.Drawing;
30 using System.IO;
31 using System.Threading.Tasks;
32 using System.Drawing.Imaging;
33
34 namespace OpenTween
35 {
36     /// <summary>
37     /// Image と Stream を対に保持するためのクラス
38     /// </summary>
39     /// <remarks>
40     /// Image.FromStream() を使用して Image を生成する場合、
41     /// Image を破棄するまでの間は元となった Stream を破棄できないためその対策として使用する。
42     /// </remarks>
43     public class MemoryImage : ICloneable, IDisposable, IEquatable<MemoryImage>
44     {
45         private readonly Image image;
46
47         /// <exception cref="InvalidImageException">
48         /// ストリームから読みだされる画像データが不正な場合にスローされる
49         /// </exception>
50         protected MemoryImage(MemoryStream stream)
51         {
52             try
53             {
54                 this.image = Image.FromStream(stream);
55             }
56             catch (ArgumentException e)
57             {
58                 stream.Dispose();
59                 throw new InvalidImageException("Invalid image", e);
60             }
61             catch (OutOfMemoryException e)
62             {
63                 // GDI+ がサポートしない画像形式で OutOfMemoryException がスローされる場合があるらしい
64                 stream.Dispose();
65                 throw new InvalidImageException("Invalid image?", e);
66             }
67             catch (ExternalException e)
68             {
69                 // 「GDI+ で汎用エラーが発生しました」という大雑把な例外がスローされる場合があるらしい
70                 stream.Dispose();
71                 throw new InvalidImageException("Invalid image?", e);
72             }
73             catch (Exception)
74             {
75                 stream.Dispose();
76                 throw;
77             }
78
79             this.Stream = stream;
80         }
81
82         /// <summary>
83         /// MemoryImage が保持している画像
84         /// </summary>
85         public Image Image
86         {
87             get
88             {
89                 if (this.IsDisposed)
90                     throw new ObjectDisposedException("this");
91
92                 return this.image;
93             }
94         }
95
96         /// <summary>
97         /// MemoryImage が保持している画像のストリーム
98         /// </summary>
99         public MemoryStream Stream { get; }
100
101         /// <summary>
102         /// MemoryImage が破棄されているか否か
103         /// </summary>
104         public bool IsDisposed { get; private set; } = false;
105
106         /// <summary>
107         /// MemoryImage が保持している画像のフォーマット
108         /// </summary>
109         public ImageFormat ImageFormat
110             => this.Image.RawFormat;
111
112         /// <summary>
113         /// MemoryImage が保持している画像のフォーマットに相当する拡張子 (ピリオド付き)
114         /// </summary>
115         public string ImageFormatExt
116         {
117             get
118             {
119                 var format = this.ImageFormat;
120
121                 // ImageFormat は == で正しく比較できないため Equals を使用する必要がある
122                 if (format.Equals(ImageFormat.Bmp))
123                     return ".bmp";
124                 if (format.Equals(ImageFormat.Emf))
125                     return ".emf";
126                 if (format.Equals(ImageFormat.Gif))
127                     return ".gif";
128                 if (format.Equals(ImageFormat.Icon))
129                     return ".ico";
130                 if (format.Equals(ImageFormat.Jpeg))
131                     return ".jpg";
132                 if (format.Equals(ImageFormat.MemoryBmp))
133                     return ".bmp";
134                 if (format.Equals(ImageFormat.Png))
135                     return ".png";
136                 if (format.Equals(ImageFormat.Tiff))
137                     return ".tiff";
138                 if (format.Equals(ImageFormat.Wmf))
139                     return ".wmf";
140
141                 // 対応する形式がなければ空文字列を返す
142                 // (上記以外のフォーマットは Image.FromStream を通過できないため、ここが実行されることはまず無い)
143                 return string.Empty;
144             }
145         }
146
147         /// <summary>
148         /// MemoryImage インスタンスを複製します
149         /// </summary>
150         /// <remarks>
151         /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
152         /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
153         /// </remarks>
154         /// <returns>複製された MemoryImage</returns>
155         public MemoryImage Clone()
156         {
157             this.Stream.Seek(0, SeekOrigin.Begin);
158
159             return MemoryImage.CopyFromStream(this.Stream);
160         }
161
162         /// <summary>
163         /// MemoryImage インスタンスを非同期に複製します
164         /// </summary>
165         /// <remarks>
166         /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
167         /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
168         /// </remarks>
169         /// <returns>複製された MemoryImage を返すタスク</returns>
170         public Task<MemoryImage> CloneAsync()
171         {
172             this.Stream.Seek(0, SeekOrigin.Begin);
173
174             return MemoryImage.CopyFromStreamAsync(this.Stream);
175         }
176
177         public override int GetHashCode()
178         {
179             using var sha1service = new System.Security.Cryptography.SHA1CryptoServiceProvider();
180             var hash = sha1service.ComputeHash(this.Stream.GetBuffer(), 0, (int)this.Stream.Length);
181             return Convert.ToBase64String(hash).GetHashCode();
182         }
183
184         public override bool Equals(object other)
185             => this.Equals(other as MemoryImage);
186
187         public bool Equals(MemoryImage other)
188         {
189             if (object.ReferenceEquals(this, other))
190                 return true;
191
192             if (other == null)
193                 return false;
194
195             // それぞれが保持する MemoryStream の内容が等しいことを検証する
196
197             var selfLength = this.Stream.Length;
198             var otherLength = other.Stream.Length;
199
200             if (selfLength != otherLength)
201                 return false;
202
203             var selfBuffer = this.Stream.GetBuffer();
204             var otherBuffer = other.Stream.GetBuffer();
205
206             for (var pos = 0L; pos < selfLength; pos++)
207             {
208                 if (selfBuffer[pos] != otherBuffer[pos])
209                     return false;
210             }
211
212             return true;
213         }
214
215         object ICloneable.Clone()
216             => this.Clone();
217
218         protected virtual void Dispose(bool disposing)
219         {
220             if (this.IsDisposed) return;
221
222             if (disposing)
223             {
224                 this.Image.Dispose();
225                 this.Stream.Dispose();
226             }
227
228             this.IsDisposed = true;
229         }
230
231         public void Dispose()
232         {
233             this.Dispose(true);
234
235             // 明示的にDisposeが呼ばれた場合はファイナライザを使用しない
236             GC.SuppressFinalize(this);
237         }
238
239         ~MemoryImage()
240             => this.Dispose(false);
241
242         /// <summary>
243         /// 指定された Stream から MemoryImage を作成します。
244         /// </summary>
245         /// <remarks>
246         /// ストリームの内容はメモリ上に展開した後に使用されるため、
247         /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
248         /// </remarks>
249         /// <param name="stream">読み込む対象となる Stream</param>
250         /// <returns>作成された MemoryImage</returns>
251         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
252         public static MemoryImage CopyFromStream(Stream stream)
253         {
254             MemoryStream memstream = null;
255             try
256             {
257                 memstream = new MemoryStream();
258
259                 stream.CopyTo(memstream);
260
261                 return new MemoryImage(memstream);
262             }
263             catch
264             {
265                 memstream?.Dispose();
266                 throw;
267             }
268         }
269
270         /// <summary>
271         /// 指定された Stream から MemoryImage を非同期に作成します。
272         /// </summary>
273         /// <remarks>
274         /// ストリームの内容はメモリ上に展開した後に使用されるため、
275         /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
276         /// </remarks>
277         /// <param name="stream">読み込む対象となる Stream</param>
278         /// <returns>作成された MemoryImage を返すタスク</returns>
279         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
280         public async static Task<MemoryImage> CopyFromStreamAsync(Stream stream)
281         {
282             MemoryStream memstream = null;
283             try
284             {
285                 memstream = new MemoryStream();
286
287                 await stream.CopyToAsync(memstream).ConfigureAwait(false);
288
289                 return new MemoryImage(memstream);
290             }
291             catch
292             {
293                 memstream?.Dispose();
294                 throw;
295             }
296         }
297
298         /// <summary>
299         /// 指定されたバイト列から MemoryImage を作成します。
300         /// </summary>
301         /// <param name="bytes">読み込む対象となるバイト列</param>
302         /// <returns>作成された MemoryImage</returns>
303         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
304         public static MemoryImage CopyFromBytes(byte[] bytes)
305         {
306             MemoryStream memstream = null;
307             try
308             {
309                 memstream = new MemoryStream(bytes);
310                 return new MemoryImage(memstream);
311             }
312             catch
313             {
314                 memstream?.Dispose();
315                 throw;
316             }
317         }
318
319         /// <summary>
320         /// Image インスタンスから MemoryImage を作成します
321         /// </summary>
322         /// <remarks>
323         /// PNG 画像として描画し直す処理を含むため、極力 Stream や byte[] を渡す他のメソッドを使用すべきです
324         /// </remarks>
325         /// <param name="image">対象となる画像</param>
326         /// <returns>作成された MemoryImage</returns>
327         public static MemoryImage CopyFromImage(Image image)
328         {
329             MemoryStream memstream = null;
330             try
331             {
332                 memstream = new MemoryStream();
333
334                 image.Save(memstream, ImageFormat.Png);
335
336                 return new MemoryImage(memstream);
337             }
338             catch
339             {
340                 memstream?.Dispose();
341                 throw;
342             }
343         }
344     }
345
346     /// <summary>
347     /// 不正な画像データに対してスローされる例外
348     /// </summary>
349     [Serializable]
350     public class InvalidImageException : Exception
351     {
352         public InvalidImageException() { }
353         public InvalidImageException(string message) : base(message) { }
354         public InvalidImageException(string message, Exception innerException) : base(message, innerException) { }
355         protected InvalidImageException(SerializationInfo info, StreamingContext context) : base(info, context) { }
356     }
357 }