OSDN Git Service

C#7.0で追加されたExpression-Bodiedメンバの構文を使用する
[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 MemoryStream stream;
46         private readonly Image image;
47
48         protected bool disposed = false;
49
50         /// <exception cref="InvalidImageException">
51         /// ストリームから読みだされる画像データが不正な場合にスローされる
52         /// </exception>
53         protected MemoryImage(MemoryStream stream)
54         {
55             try
56             {
57                 this.image = Image.FromStream(stream);
58             }
59             catch (ArgumentException e)
60             {
61                 stream.Dispose();
62                 throw new InvalidImageException("Invalid image", e);
63             }
64             catch (OutOfMemoryException e)
65             {
66                 // GDI+ がサポートしない画像形式で OutOfMemoryException がスローされる場合があるらしい
67                 stream.Dispose();
68                 throw new InvalidImageException("Invalid image?", e);
69             }
70             catch (ExternalException e)
71             {
72                 // 「GDI+ で汎用エラーが発生しました」という大雑把な例外がスローされる場合があるらしい
73                 stream.Dispose();
74                 throw new InvalidImageException("Invalid image?", e);
75             }
76             catch (Exception)
77             {
78                 stream.Dispose();
79                 throw;
80             }
81
82             this.stream = stream;
83         }
84
85         /// <summary>
86         /// MemoryImage が保持している画像
87         /// </summary>
88         public Image Image
89         {
90             get
91             {
92                 if (this.IsDisposed)
93                     throw new ObjectDisposedException("this");
94
95                 return this.image;
96             }
97         }
98
99         /// <summary>
100         /// MemoryImage が保持している画像のストリーム
101         /// </summary>
102         public MemoryStream Stream
103         {
104             // MemoryStream は破棄されていても一部のメソッドが使用可能なためここでは例外を投げない
105             get { return this.stream; }
106         }
107
108         /// <summary>
109         /// MemoryImage が破棄されているか否か
110         /// </summary>
111         public bool IsDisposed
112         {
113             get { return this.disposed; }
114         }
115
116         /// <summary>
117         /// MemoryImage が保持している画像のフォーマット
118         /// </summary>
119         public ImageFormat ImageFormat
120         {
121             get { return this.Image.RawFormat; }
122         }
123
124         /// <summary>
125         /// MemoryImage が保持している画像のフォーマットに相当する拡張子 (ピリオド付き)
126         /// </summary>
127         public string ImageFormatExt
128         {
129             get
130             {
131                 var format = this.ImageFormat;
132
133                 // ImageFormat は == で正しく比較できないため Equals を使用する必要がある
134                 if (format.Equals(ImageFormat.Bmp))
135                     return ".bmp";
136                 if (format.Equals(ImageFormat.Emf))
137                     return ".emf";
138                 if (format.Equals(ImageFormat.Gif))
139                     return ".gif";
140                 if (format.Equals(ImageFormat.Icon))
141                     return ".ico";
142                 if (format.Equals(ImageFormat.Jpeg))
143                     return ".jpg";
144                 if (format.Equals(ImageFormat.MemoryBmp))
145                     return ".bmp";
146                 if (format.Equals(ImageFormat.Png))
147                     return ".png";
148                 if (format.Equals(ImageFormat.Tiff))
149                     return ".tiff";
150                 if (format.Equals(ImageFormat.Wmf))
151                     return ".wmf";
152
153                 // 対応する形式がなければ空文字列を返す
154                 // (上記以外のフォーマットは Image.FromStream を通過できないため、ここが実行されることはまず無い)
155                 return string.Empty;
156             }
157         }
158
159         /// <summary>
160         /// MemoryImage インスタンスを複製します
161         /// </summary>
162         /// <remarks>
163         /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
164         /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
165         /// </remarks>
166         /// <returns>複製された MemoryImage</returns>
167         public MemoryImage Clone()
168         {
169             this.Stream.Seek(0, SeekOrigin.Begin);
170
171             return MemoryImage.CopyFromStream(this.Stream);
172         }
173
174         /// <summary>
175         /// MemoryImage インスタンスを非同期に複製します
176         /// </summary>
177         /// <remarks>
178         /// メソッド実行中にストリームのシークが行われないよう注意して下さい。
179         /// 特に PictureBox で Gif アニメーションを表示している場合は Enabled に false をセットするなどして更新を止めて下さい。
180         /// </remarks>
181         /// <returns>複製された MemoryImage を返すタスク</returns>
182         public Task<MemoryImage> CloneAsync()
183         {
184             this.Stream.Seek(0, SeekOrigin.Begin);
185
186             return MemoryImage.CopyFromStreamAsync(this.Stream);
187         }
188
189         public override int GetHashCode()
190         {
191             using (var sha1service = new System.Security.Cryptography.SHA1CryptoServiceProvider())
192             {
193                 var hash = sha1service.ComputeHash(this.Stream.GetBuffer(), 0, (int)this.Stream.Length);
194                 return Convert.ToBase64String(hash).GetHashCode();
195             }
196         }
197
198         public override bool Equals(object other)
199         {
200             return this.Equals(other as MemoryImage);
201         }
202
203         public bool Equals(MemoryImage other)
204         {
205             if (object.ReferenceEquals(this, other))
206                 return true;
207
208             if (other == null)
209                 return false;
210
211             // それぞれが保持する MemoryStream の内容が等しいことを検証する
212
213             var selfLength = this.Stream.Length;
214             var otherLength = other.Stream.Length;
215
216             if (selfLength != otherLength)
217                 return false;
218
219             var selfBuffer = this.Stream.GetBuffer();
220             var otherBuffer = other.Stream.GetBuffer();
221
222             for (var pos = 0L; pos < selfLength; pos++)
223             {
224                 if (selfBuffer[pos] != otherBuffer[pos])
225                     return false;
226             }
227
228             return true;
229         }
230
231         object ICloneable.Clone()
232         {
233             return this.Clone();
234         }
235
236         protected virtual void Dispose(bool disposing)
237         {
238             if (this.disposed) return;
239
240             if (disposing)
241             {
242                 this.Image.Dispose();
243                 this.Stream.Dispose();
244             }
245
246             this.disposed = true;
247         }
248
249         public void Dispose()
250         {
251             this.Dispose(true);
252
253             // 明示的にDisposeが呼ばれた場合はファイナライザを使用しない
254             GC.SuppressFinalize(this);
255         }
256
257         ~MemoryImage()
258             => this.Dispose(false);
259
260         /// <summary>
261         /// 指定された Stream から MemoryImage を作成します。
262         /// </summary>
263         /// <remarks>
264         /// ストリームの内容はメモリ上に展開した後に使用されるため、
265         /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
266         /// </remarks>
267         /// <param name="stream">読み込む対象となる Stream</param>
268         /// <returns>作成された MemoryImage</returns>
269         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
270         public static MemoryImage CopyFromStream(Stream stream)
271         {
272             MemoryStream memstream = null;
273             try
274             {
275                 memstream = new MemoryStream();
276
277                 stream.CopyTo(memstream);
278
279                 return new MemoryImage(memstream);
280             }
281             catch
282             {
283                 memstream?.Dispose();
284                 throw;
285             }
286         }
287
288         /// <summary>
289         /// 指定された Stream から MemoryImage を非同期に作成します。
290         /// </summary>
291         /// <remarks>
292         /// ストリームの内容はメモリ上に展開した後に使用されるため、
293         /// 引数に指定した Stream を MemoryImage より先に破棄しても問題ありません。
294         /// </remarks>
295         /// <param name="stream">読み込む対象となる Stream</param>
296         /// <returns>作成された MemoryImage を返すタスク</returns>
297         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
298         public async static Task<MemoryImage> CopyFromStreamAsync(Stream stream)
299         {
300             MemoryStream memstream = null;
301             try
302             {
303                 memstream = new MemoryStream();
304
305                 await stream.CopyToAsync(memstream).ConfigureAwait(false);
306
307                 return new MemoryImage(memstream);
308             }
309             catch
310             {
311                 memstream?.Dispose();
312                 throw;
313             }
314         }
315
316         /// <summary>
317         /// 指定されたバイト列から MemoryImage を作成します。
318         /// </summary>
319         /// <param name="bytes">読み込む対象となるバイト列</param>
320         /// <returns>作成された MemoryImage</returns>
321         /// <exception cref="InvalidImageException">不正な画像データが入力された場合</exception>
322         public static MemoryImage CopyFromBytes(byte[] bytes)
323         {
324             MemoryStream memstream = null;
325             try
326             {
327                 memstream = new MemoryStream(bytes);
328                 return new MemoryImage(memstream);
329             }
330             catch
331             {
332                 memstream?.Dispose();
333                 throw;
334             }
335         }
336
337         /// <summary>
338         /// Image インスタンスから MemoryImage を作成します
339         /// </summary>
340         /// <remarks>
341         /// PNG 画像として描画し直す処理を含むため、極力 Stream や byte[] を渡す他のメソッドを使用すべきです
342         /// </remarks>
343         /// <param name="image">対象となる画像</param>
344         /// <returns>作成された MemoryImage</returns>
345         public static MemoryImage CopyFromImage(Image image)
346         {
347             MemoryStream memstream = null;
348             try
349             {
350                 memstream = new MemoryStream();
351
352                 image.Save(memstream, ImageFormat.Png);
353
354                 return new MemoryImage(memstream);
355             }
356             catch
357             {
358                 memstream?.Dispose();
359                 throw;
360             }
361         }
362     }
363
364     /// <summary>
365     /// 不正な画像データに対してスローされる例外
366     /// </summary>
367     [Serializable]
368     public class InvalidImageException : Exception
369     {
370         public InvalidImageException() { }
371         public InvalidImageException(string message) : base(message) { }
372         public InvalidImageException(string message, Exception innerException) : base(message, innerException) { }
373         protected InvalidImageException(SerializationInfo info, StreamingContext context) : base(info, context) { }
374     }
375 }