GifImage.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. using System;
  2. using System.ComponentModel;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Runtime.Versioning;
  7. using System.Security.Permissions;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Interop;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Resources;
  14. using HandyControl.Data;
  15. using HandyControl.Tools;
  16. using HandyControl.Tools.Interop;
  17. namespace HandyControl.Controls;
  18. public class GifImage : Image, IDisposable
  19. {
  20. private static readonly Guid GifGuid = new("{b96b3caa-0728-11d3-9d7b-0000f81ef32e}");
  21. private static readonly Guid GifSingleFrameGuid = new("{b96b3cb0-0728-11d3-9d7b-0000f81ef32e}");
  22. internal IntPtr NativeImage;
  23. private byte[] _rawData;
  24. private bool _isStart;
  25. private bool _isLoaded;
  26. static GifImage()
  27. {
  28. VisibilityProperty.OverrideMetadata(typeof(GifImage), new PropertyMetadata(default(Visibility), OnVisibilityChanged));
  29. }
  30. public static readonly DependencyProperty UriProperty = DependencyProperty.Register(
  31. nameof(Uri), typeof(Uri), typeof(GifImage), new PropertyMetadata(default(Uri), OnUriChanged));
  32. private static void OnUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  33. {
  34. var ctl = (GifImage) d;
  35. ctl.StopAnimate();
  36. if (e.NewValue != null)
  37. {
  38. var v = (Uri) e.NewValue;
  39. ctl.GetGifStreamFromPack(v);
  40. ctl.StartAnimate();
  41. }
  42. else
  43. {
  44. ctl.Source = null;
  45. }
  46. }
  47. public Uri Uri
  48. {
  49. get => (Uri) GetValue(UriProperty);
  50. set => SetValue(UriProperty, value);
  51. }
  52. private static void OnVisibilityChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
  53. {
  54. var ctl = (GifImage) s;
  55. if (ctl.NativeImage == IntPtr.Zero) return;
  56. var v = (Visibility) e.NewValue;
  57. if (v != Visibility.Visible)
  58. {
  59. ctl.StopAnimate();
  60. }
  61. else if (!ctl._isStart)
  62. {
  63. ctl.StartAnimate();
  64. }
  65. }
  66. ~GifImage() => Dispose(false);
  67. public GifImage()
  68. {
  69. Loaded += (s, e) =>
  70. {
  71. if (DesignerProperties.GetIsInDesignMode(this)) return;
  72. if (_isLoaded) return;
  73. _isLoaded = true;
  74. if (Uri != null)
  75. {
  76. GetGifStreamFromPack(Uri);
  77. StartAnimate();
  78. }
  79. else if (Source is BitmapImage image)
  80. {
  81. GetGifStreamFromPack(image.UriSource);
  82. StartAnimate();
  83. }
  84. };
  85. Unloaded += (s, e) => Dispose();
  86. }
  87. public GifImage(string filename)
  88. {
  89. Source = null;
  90. CreateSourceFromFile(filename);
  91. StartAnimate();
  92. }
  93. public GifImage(Stream stream)
  94. {
  95. Source = null;
  96. CreateSourceFromStream(stream);
  97. StartAnimate();
  98. }
  99. public void Dispose()
  100. {
  101. Dispose(true);
  102. GC.SuppressFinalize(this);
  103. }
  104. protected virtual void Dispose(bool disposing)
  105. {
  106. if (NativeImage == IntPtr.Zero) return;
  107. try
  108. {
  109. StopAnimate();
  110. InteropMethods.Gdip.GdipDisposeImage(new HandleRef(this, NativeImage));
  111. }
  112. catch
  113. {
  114. // ignored
  115. }
  116. finally
  117. {
  118. NativeImage = IntPtr.Zero;
  119. }
  120. }
  121. private void CreateSourceFromFile(string filename)
  122. {
  123. filename = Path.GetFullPath(filename);
  124. var status = InteropMethods.Gdip.GdipCreateBitmapFromFile(filename, out var bitmap);
  125. if (status != InteropMethods.Gdip.Ok)
  126. throw InteropMethods.Gdip.StatusException(status);
  127. status = InteropMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
  128. if (status != InteropMethods.Gdip.Ok)
  129. {
  130. InteropMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
  131. throw InteropMethods.Gdip.StatusException(status);
  132. }
  133. SetNativeImage(bitmap);
  134. EnsureSave(this, filename, null);
  135. }
  136. private void CreateSourceFromStream(Stream stream)
  137. {
  138. if (stream == null)
  139. throw new ArgumentException("stream null");
  140. var status = InteropMethods.Gdip.GdipCreateBitmapFromStream(new GPStream(stream), out var bitmap);
  141. if (status != InteropMethods.Gdip.Ok)
  142. throw InteropMethods.Gdip.StatusException(status);
  143. status = InteropMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
  144. if (status != InteropMethods.Gdip.Ok)
  145. {
  146. InteropMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
  147. throw InteropMethods.Gdip.StatusException(status);
  148. }
  149. SetNativeImage(bitmap);
  150. EnsureSave(this, null, stream);
  151. }
  152. private void GetGifStreamFromPack(Uri uri)
  153. {
  154. try
  155. {
  156. StreamResourceInfo streamInfo;
  157. if (!uri.IsAbsoluteUri)
  158. {
  159. streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
  160. }
  161. else
  162. {
  163. if (uri.GetLeftPart(UriPartial.Authority).Contains("siteoforigin"))
  164. {
  165. streamInfo = Application.GetRemoteStream(uri);
  166. }
  167. else
  168. {
  169. streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
  170. }
  171. }
  172. if (streamInfo == null)
  173. {
  174. throw new FileNotFoundException("Resource not found.", uri.ToString());
  175. }
  176. CreateSourceFromStream(streamInfo.Stream);
  177. }
  178. catch
  179. {
  180. // ignored
  181. }
  182. }
  183. private void SwitchToCommonImage()
  184. {
  185. if (Source == null && Uri != null)
  186. {
  187. SetCurrentValue(SourceProperty, new BitmapImage(Uri));
  188. }
  189. }
  190. [ResourceExposure(ResourceScope.None)]
  191. [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
  192. internal static void EnsureSave(GifImage image, string filename, Stream dataStream)
  193. {
  194. if (!image.RawGuid.Equals(GifGuid) && !image.RawGuid.Equals(GifSingleFrameGuid))
  195. {
  196. image.SwitchToCommonImage();
  197. return;
  198. }
  199. var animatedGif = false;
  200. var dimensions = image.FrameDimensionsList;
  201. if (dimensions.Select(guid => new GifFrameDimension(guid)).Contains(GifFrameDimension.Time))
  202. {
  203. animatedGif = image.GetFrameCount(GifFrameDimension.Time) > 1;
  204. }
  205. if (!animatedGif)
  206. {
  207. image.SwitchToCommonImage();
  208. return;
  209. }
  210. try
  211. {
  212. Stream created = null;
  213. long lastPos = 0;
  214. if (dataStream != null)
  215. {
  216. lastPos = dataStream.Position;
  217. dataStream.Position = 0;
  218. }
  219. try
  220. {
  221. if (dataStream == null)
  222. {
  223. created = dataStream = File.OpenRead(filename);
  224. }
  225. image._rawData = new byte[(int) dataStream.Length];
  226. dataStream.Read(image._rawData, 0, (int) dataStream.Length);
  227. }
  228. finally
  229. {
  230. if (created != null)
  231. {
  232. created.Close();
  233. }
  234. else
  235. {
  236. // ReSharper disable once PossibleNullReferenceException
  237. dataStream.Position = lastPos;
  238. }
  239. }
  240. }
  241. // possible exceptions for reading the filename
  242. catch (UnauthorizedAccessException)
  243. {
  244. }
  245. catch (DirectoryNotFoundException)
  246. {
  247. }
  248. catch (IOException)
  249. {
  250. }
  251. // possible exceptions for setting/getting the position inside dataStream
  252. catch (NotSupportedException)
  253. {
  254. }
  255. catch (ObjectDisposedException)
  256. {
  257. }
  258. // possible exception when reading stuff into dataStream
  259. catch (ArgumentException)
  260. {
  261. }
  262. }
  263. internal void SetNativeImage(IntPtr handle)
  264. {
  265. if (handle == IntPtr.Zero)
  266. throw new ArgumentException("NativeHandle0");
  267. NativeImage = handle;
  268. }
  269. internal Guid RawGuid
  270. {
  271. get
  272. {
  273. var guid = new Guid();
  274. var status = InteropMethods.Gdip.GdipGetImageRawFormat(new HandleRef(this, NativeImage), ref guid);
  275. if (status != InteropMethods.Gdip.Ok)
  276. throw InteropMethods.Gdip.StatusException(status);
  277. return guid;
  278. }
  279. }
  280. internal void StartAnimate()
  281. {
  282. ImageAnimator.Animate(this, OnFrameChanged);
  283. _isStart = true;
  284. }
  285. internal void StopAnimate()
  286. {
  287. ImageAnimator.StopAnimate(this, OnFrameChanged);
  288. _isStart = false;
  289. }
  290. internal Guid[] FrameDimensionsList
  291. {
  292. get
  293. {
  294. var status = InteropMethods.Gdip.GdipImageGetFrameDimensionsCount(new HandleRef(this, NativeImage), out var count);
  295. if (status != InteropMethods.Gdip.Ok)
  296. {
  297. throw InteropMethods.Gdip.StatusException(status);
  298. }
  299. if (count <= 0)
  300. {
  301. return new Guid[0];
  302. }
  303. var size = Marshal.SizeOf(typeof(Guid));
  304. var buffer = Marshal.AllocHGlobal(checked(size * count));
  305. if (buffer == IntPtr.Zero)
  306. {
  307. throw InteropMethods.Gdip.StatusException(InteropMethods.Gdip.OutOfMemory);
  308. }
  309. status = InteropMethods.Gdip.GdipImageGetFrameDimensionsList(new HandleRef(this, NativeImage), buffer, count);
  310. if (status != InteropMethods.Gdip.Ok)
  311. {
  312. Marshal.FreeHGlobal(buffer);
  313. throw InteropMethods.Gdip.StatusException(status);
  314. }
  315. var guids = new Guid[count];
  316. try
  317. {
  318. for (var i = 0; i < count; i++)
  319. {
  320. guids[i] = (Guid) InteropMethods.PtrToStructure((IntPtr) ((long) buffer + size * i), typeof(Guid));
  321. }
  322. }
  323. finally
  324. {
  325. Marshal.FreeHGlobal(buffer);
  326. }
  327. return guids;
  328. }
  329. }
  330. internal int GetFrameCount(GifFrameDimension dimension)
  331. {
  332. var count = new[] { 0 };
  333. var dimensionId = dimension.Guid;
  334. var status = InteropMethods.Gdip.GdipImageGetFrameCount(new HandleRef(this, NativeImage), ref dimensionId, count);
  335. if (status != InteropMethods.Gdip.Ok)
  336. throw InteropMethods.Gdip.StatusException(status);
  337. return count[0];
  338. }
  339. internal GifPropertyItem GetPropertyItem(int propid)
  340. {
  341. GifPropertyItem propitem;
  342. var status = InteropMethods.Gdip.GdipGetPropertyItemSize(new HandleRef(this, NativeImage), propid, out var size);
  343. if (status != InteropMethods.Gdip.Ok)
  344. throw InteropMethods.Gdip.StatusException(status);
  345. if (size == 0)
  346. return null;
  347. var propdata = Marshal.AllocHGlobal(size);
  348. if (propdata == IntPtr.Zero)
  349. throw InteropMethods.Gdip.StatusException(InteropMethods.Gdip.OutOfMemory);
  350. status = InteropMethods.Gdip.GdipGetPropertyItem(new HandleRef(this, NativeImage), propid, size, propdata);
  351. try
  352. {
  353. if (status != InteropMethods.Gdip.Ok)
  354. {
  355. throw InteropMethods.Gdip.StatusException(status);
  356. }
  357. propitem = GifPropertyItemInternal.ConvertFromMemory(propdata, 1)[0];
  358. }
  359. finally
  360. {
  361. Marshal.FreeHGlobal(propdata);
  362. }
  363. return propitem;
  364. }
  365. internal int SelectActiveFrame(GifFrameDimension dimension, int frameIndex)
  366. {
  367. var count = new[] { 0 };
  368. var dimensionId = dimension.Guid;
  369. var status = InteropMethods.Gdip.GdipImageSelectActiveFrame(new HandleRef(this, NativeImage), ref dimensionId, frameIndex);
  370. if (status != InteropMethods.Gdip.Ok)
  371. throw InteropMethods.Gdip.StatusException(status);
  372. return count[0];
  373. }
  374. [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
  375. [EditorBrowsable(EditorBrowsableState.Advanced)]
  376. [ResourceExposure(ResourceScope.Machine)]
  377. [ResourceConsumption(ResourceScope.Machine)]
  378. internal IntPtr GetHbitmap() => GetHbitmap(Colors.LightGray);
  379. [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
  380. [EditorBrowsable(EditorBrowsableState.Advanced)]
  381. [ResourceExposure(ResourceScope.Machine)]
  382. [ResourceConsumption(ResourceScope.Machine)]
  383. internal IntPtr GetHbitmap(Color background)
  384. {
  385. var status = InteropMethods.Gdip.GdipCreateHBITMAPFromBitmap(new HandleRef(this, NativeImage), out var hBitmap,
  386. ColorHelper.ToWin32(background));
  387. if (status == 2 && (Width >= short.MaxValue || Height >= short.MaxValue))
  388. {
  389. throw new ArgumentException("GdiplusInvalidSize");
  390. }
  391. if (status != InteropMethods.Gdip.Ok && NativeImage != IntPtr.Zero)
  392. {
  393. throw InteropMethods.Gdip.StatusException(status);
  394. }
  395. return hBitmap;
  396. }
  397. private void GetBitmapSource()
  398. {
  399. var handle = IntPtr.Zero;
  400. try
  401. {
  402. handle = GetHbitmap();
  403. Source = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty,
  404. BitmapSizeOptions.FromEmptyOptions());
  405. }
  406. catch
  407. {
  408. // ignored
  409. }
  410. finally
  411. {
  412. if (handle != IntPtr.Zero)
  413. {
  414. InteropMethods.DeleteObject(handle);
  415. }
  416. }
  417. }
  418. private void OnFrameChanged(object sender, EventArgs e)
  419. {
  420. Dispatcher.BeginInvoke(new Action(() =>
  421. {
  422. ImageAnimator.UpdateFrames();
  423. Source?.Freeze();
  424. GetBitmapSource();
  425. InvalidateVisual();
  426. }));
  427. }
  428. }