DFEmbeddedImageControl.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. using InABox.Core;
  2. using System;
  3. using System.Drawing;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Media.Imaging;
  7. using System.Windows.Media;
  8. using InABox.WPF;
  9. using Color = System.Drawing.Color;
  10. using Image = System.Windows.Controls.Image;
  11. using System.Diagnostics.CodeAnalysis;
  12. using System.IO;
  13. using System.Linq;
  14. using Microsoft.Win32;
  15. using Syncfusion.UI.Xaml.ImageEditor;
  16. using InABox.Wpf;
  17. namespace InABox.DynamicGrid
  18. {
  19. public static class EmbeddedImageUtilities
  20. {
  21. private static BitmapImage? EmptyImage;
  22. private static BitmapImage? EmptySignature;
  23. public static ImageSource CreateEmptyImage()
  24. {
  25. EmptyImage ??= new Bitmap(256, 256).WatermarkImage("No Image", Color.Gray).AsBitmapImage();
  26. return EmptyImage;
  27. }
  28. public static ImageSource CreateEmptySignature()
  29. {
  30. EmptySignature ??= new Bitmap(400, 400).WatermarkImage("No Signature", Color.Gray).AsBitmapImage();
  31. return EmptySignature;
  32. }
  33. public static bool SelectImageFile([NotNullWhen(true)] out byte[]? data)
  34. {
  35. var dlg = new OpenFileDialog();
  36. dlg.Filter = "Image Files (*.jpg;*.png;*.bmp)|*.png;*.jpg;*.jpeg;*.bmp";
  37. if (dlg.ShowDialog() == true)
  38. {
  39. var filename = Path.GetFileName(dlg.FileName).ToLower();
  40. var timestamp = new FileInfo(dlg.FileName).LastWriteTime;
  41. data = File.ReadAllBytes(dlg.FileName);
  42. return true;
  43. }
  44. data = null;
  45. return false;
  46. }
  47. public static byte[]? SaveImageToBytes(Image? image, bool isEmpty, BitmapEncoder encoder)
  48. {
  49. if (image == null
  50. || image.Source is not BitmapImage bmpimg) return null;
  51. if (isEmpty) return null;
  52. byte[] data;
  53. encoder.Frames.Add(BitmapFrame.Create(bmpimg));
  54. using (var ms = new MemoryStream())
  55. {
  56. encoder.Save(ms);
  57. ms.Seek(0, SeekOrigin.Begin);
  58. data = ms.ToArray();
  59. }
  60. return data;
  61. }
  62. public static string SaveImageToString(Image? image, bool isEmpty, BitmapEncoder encoder)
  63. {
  64. var data = SaveImageToBytes(image, isEmpty, encoder);
  65. return data != null ? Convert.ToBase64String(data) : "";
  66. }
  67. /// <summary>
  68. /// Loads a value into an Image object.
  69. /// </summary>
  70. /// <param name="image"></param>
  71. /// <param name="value"></param>
  72. /// <param name="defImage"></param>
  73. /// <returns><see langword="false"/> if the image is empty.</returns>
  74. public static bool LoadImageFromData(Image? image, object? value, Func<ImageSource>? defImage = null)
  75. {
  76. if (image == null) return false;
  77. BitmapImage? source = null;
  78. try
  79. {
  80. if (value is string str)
  81. {
  82. source = ImageUtils.BitmapImageFromBase64(str);
  83. }
  84. else if (value is byte[] data)
  85. {
  86. source = ImageUtils.BitmapImageFromBytes(data);
  87. }
  88. }
  89. catch (Exception e)
  90. {
  91. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  92. }
  93. if (source != null)
  94. {
  95. image.Source = source;
  96. return true;
  97. }
  98. else
  99. {
  100. image.Source = defImage?.Invoke() ?? CreateEmptyImage();
  101. return false;
  102. }
  103. }
  104. }
  105. public class DFEmbeddedImageControl : DynamicFormFieldControl<DFLayoutEmbeddedImage, DFLayoutEmbeddedImageProperties, Guid, DFLayoutEmbeddedMediaValue>
  106. {
  107. private Grid _grid = null!; // Late-initialised
  108. private Border? _border;
  109. private Image _image = null!; // Late-initialised
  110. private bool _isEmpty = true;
  111. private Button viewButton = null!;
  112. private Button pasteButton = null!;
  113. private Button clearButton = null!;
  114. private Button selectButton = null!;
  115. private DFLayoutEmbeddedMediaValue _value = null!;
  116. static DFEmbeddedImageControl()
  117. {
  118. IsEnabledProperty.OverrideMetadata(
  119. typeof(DFEmbeddedImageControl),
  120. new UIPropertyMetadata(
  121. true,
  122. ControlIsEnabledChanged));
  123. }
  124. private static void ControlIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  125. {
  126. var control = (DFEmbeddedImageControl)obj;
  127. control.IsEnabled = true;
  128. var enabled = (bool)e.NewValue;
  129. control.pasteButton.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
  130. control.clearButton.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
  131. control.selectButton.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed;
  132. }
  133. protected override FrameworkElement Create()
  134. {
  135. _value = new DFLayoutEmbeddedMediaValue();
  136. _grid = new Grid();
  137. _grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
  138. _grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
  139. _grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
  140. _grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
  141. _grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
  142. _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
  143. _grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
  144. _border = new Border()
  145. {
  146. BorderBrush = new SolidColorBrush(Colors.Gray),
  147. BorderThickness = new Thickness(0.75),
  148. Background = new SolidColorBrush(Colors.White),
  149. Padding = new Thickness(4),
  150. Margin = new Thickness(0)
  151. };
  152. _border.SetGridPosition(0,0,5,1);
  153. _grid.Children.Add(_border);
  154. _image = new Image
  155. {
  156. Stretch = Stretch.Uniform,
  157. //StretchDirection = StretchDirection.DownOnly,
  158. Source = EmbeddedImageUtilities.CreateEmptyImage(),
  159. };
  160. _border.Child = _image;
  161. viewButton = new Button
  162. {
  163. Content = Content = new Image { Source = Wpf.Resources.zoomin.AsBitmapImage(24, 24), Margin = new Thickness(2) },
  164. Margin = new Thickness(5, 0, 0, 0),
  165. Width = 30,
  166. Height = 30,
  167. ToolTip = "View Image"
  168. };
  169. viewButton.Click += ViewButton_Click;
  170. viewButton.SetGridPosition(0,1);
  171. _grid.Children.Add(viewButton);
  172. pasteButton = new Button
  173. {
  174. Content = Content = new Image { Source = Wpf.Resources.paste.AsBitmapImage(24, 24) },
  175. Margin = new Thickness(5, 5, 0, 0),
  176. Width = 30,
  177. Height = 30,
  178. ToolTip = "Paste Image"
  179. };
  180. pasteButton.Click += PasteButton_Click;
  181. pasteButton.SetGridPosition(2, 1);
  182. _grid.Children.Add(pasteButton);
  183. clearButton = new Button
  184. {
  185. Content = Content = new Image { Source = Wpf.Resources.delete.AsBitmapImage(24, 24) },
  186. Margin = new Thickness(5, 5, 0, 0),
  187. Width = 30,
  188. Height = 30,
  189. ToolTip = "Clear Image"
  190. };
  191. clearButton.Click += EmbeddedImageClear_Click;
  192. clearButton.SetGridPosition(3,1);
  193. _grid.Children.Add(clearButton);
  194. selectButton = new Button
  195. {
  196. Content = Content = new Image { Source = Wpf.Resources.load.AsBitmapImage(24, 24) },
  197. Margin = new Thickness(5, 5, 0, 0),
  198. Width = 30,
  199. Height = 30,
  200. ToolTip = "Upload Image"
  201. };
  202. selectButton.Click += EmbeddedImageSelect_Click;
  203. selectButton.SetGridPosition(4, 1);
  204. _grid.Children.Add(selectButton);
  205. return _grid;
  206. }
  207. private static void ShowImage(byte[] data)
  208. {
  209. var src = ImageUtils.LoadImage(data);
  210. var editor = new SfImageEditor
  211. {
  212. ImageSource = src
  213. };
  214. var window = new Window() { Content = editor, Height = 800, Width=800 };
  215. window.ShowDialog();
  216. }
  217. private void ViewButton_Click(object sender, RoutedEventArgs e)
  218. {
  219. if (_value.Data?.Any() == true)
  220. ShowImage(_value.Data);
  221. else
  222. {
  223. DigitalFormDocumentFactory.LoadDocument(_value.ID, (data) =>
  224. {
  225. if(data.Length == 0)
  226. {
  227. Dispatcher.BeginInvoke(() =>
  228. MessageWindow.ShowMessage("The image should be in the database, but it is not.", "Document not found", image: MessageWindow.WarningImage));
  229. }
  230. else
  231. {
  232. _value.Data = data;
  233. Dispatcher.BeginInvoke(() => ShowImage(data));
  234. }
  235. });
  236. }
  237. }
  238. private void PasteButton_Click(object sender, RoutedEventArgs e)
  239. {
  240. if (Clipboard.ContainsImage())
  241. {
  242. var paste = Clipboard.GetImage();
  243. var bitmap = ImageUtils.BitmapSourceToBitmap(paste);
  244. var img = ImageUtils.ToBitmapImage(bitmap);
  245. var empty = img == null;
  246. if (_isEmpty != empty)
  247. {
  248. _isEmpty = empty;
  249. if (empty)
  250. _image.Source = EmbeddedImageUtilities.CreateEmptyImage();
  251. else
  252. _image.Source = img;
  253. SetSerializedValue(new DFLayoutEmbeddedMediaValue
  254. {
  255. Data = EmbeddedImageUtilities.SaveImageToBytes(_image, _isEmpty, new JpegBitmapEncoder()) ?? Array.Empty<byte>()
  256. });
  257. ChangeField();
  258. }
  259. }
  260. }
  261. public override void SetSerializedValue(DFLayoutEmbeddedMediaValue value)
  262. {
  263. _value = value;
  264. _value.Thumbnail ??= _value.Data != null
  265. ? ImageUtils.BitmapImageFromBytes(_value.Data)?.Resize(200, 200).ToArray<BmpBitmapEncoder>()
  266. : null;
  267. _isEmpty = !EmbeddedImageUtilities.LoadImageFromData(_image, _value.Data?.Any() == true ? _value.Data : _value.Thumbnail);
  268. }
  269. public override DFLayoutEmbeddedMediaValue GetSerializedValue()
  270. {
  271. if ((_value.Data?.Any() == true) && (_value.ID == Guid.Empty))
  272. _value.ID = DigitalFormDocumentFactory.SaveDocument(_value.Data);
  273. return _value;
  274. }
  275. public override Guid GetValue()
  276. {
  277. return GetSerializedValue().ID;
  278. }
  279. public override void SetValue(Guid value)
  280. {
  281. SetSerializedValue(new DFLayoutEmbeddedMediaValue() { ID = value });
  282. DigitalFormDocumentFactory.LoadDocument(value, (data) =>
  283. {
  284. if(data.Length > 0)
  285. {
  286. SetSerializedValue(new() { ID = value, Data = data });
  287. }
  288. });
  289. }
  290. private void EmbeddedImageClear_Click(object sender, RoutedEventArgs e)
  291. {
  292. if (!_isEmpty)
  293. {
  294. _image.Source = EmbeddedImageUtilities.CreateEmptyImage();
  295. _isEmpty = true;
  296. SetSerializedValue(new DFLayoutEmbeddedMediaValue());
  297. ChangeField();
  298. }
  299. }
  300. private void EmbeddedImageSelect_Click(object sender, RoutedEventArgs e)
  301. {
  302. if (EmbeddedImageUtilities.SelectImageFile(out var data))
  303. {
  304. var img = ImageUtils.BitmapImageFromBytes(data);
  305. var empty = img == null;
  306. if (_isEmpty != empty)
  307. {
  308. _isEmpty = empty;
  309. if (empty)
  310. _image.Source = EmbeddedImageUtilities.CreateEmptyImage();
  311. else
  312. _image.Source = img;
  313. SetSerializedValue(new DFLayoutEmbeddedMediaValue
  314. {
  315. Data = data
  316. });
  317. ChangeField();
  318. }
  319. }
  320. }
  321. protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  322. {
  323. base.OnPropertyChanged(e);
  324. if (e.Property == IsEnabledProperty)
  325. {
  326. _grid.RowDefinitions[1].Height = (bool)e.NewValue
  327. ? GridLength.Auto
  328. : new GridLength(0);
  329. }
  330. }
  331. protected override bool IsEmpty() => _isEmpty;
  332. }
  333. }