ClipboardController.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. using PixiEditor.Exceptions;
  2. using PixiEditor.Helpers;
  3. using PixiEditor.Helpers.Extensions;
  4. using PixiEditor.Models.DataHolders;
  5. using PixiEditor.Models.ImageManipulation;
  6. using PixiEditor.Models.IO;
  7. using PixiEditor.Models.Layers;
  8. using PixiEditor.Models.Position;
  9. using PixiEditor.Models.Undo;
  10. using PixiEditor.Parser;
  11. using PixiEditor.ViewModels;
  12. using SkiaSharp;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Collections.Specialized;
  16. using System.IO;
  17. using System.Linq;
  18. using System.Windows;
  19. using System.Windows.Media;
  20. using System.Windows.Media.Imaging;
  21. namespace PixiEditor.Models.Controllers
  22. {
  23. public static class ClipboardController
  24. {
  25. public static readonly string TempCopyFilePath = Path.Join(
  26. Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
  27. "PixiEditor",
  28. "Copied.png");
  29. /// <summary>
  30. /// Copies the selection to clipboard in PNG, Bitmap and DIB formats. <para/>
  31. /// Also serailizes the <paramref name="document"/> in the PIXI format and copies it to the clipboard.
  32. /// </summary>
  33. public static void CopyToClipboard(Document document)
  34. {
  35. CopyToClipboard(
  36. document.Layers.Where(x => document.GetFinalLayerIsVisible(x) && x.IsActive).ToArray(),
  37. document.ActiveSelection.SelectionLayer,
  38. document.LayerStructure,
  39. document.Width,
  40. document.Height,
  41. null/*document.ToSerializable()*/);
  42. }
  43. private static Surface CreateMaskedCombinedSurface(Layer[] layers, LayerStructure structure, Layer selLayer)
  44. {
  45. if (layers.Length == 0)
  46. throw new ArgumentException("Can't combine 0 layers");
  47. selLayer.ClipCanvas();
  48. Surface combined = BitmapUtils.CombineLayers(new Int32Rect(selLayer.OffsetX, selLayer.OffsetY, selLayer.Width, selLayer.Height), layers, structure);
  49. using SKImage snapshot = selLayer.LayerBitmap.SkiaSurface.Snapshot();
  50. combined.SkiaSurface.Canvas.DrawImage(snapshot, 0, 0, Surface.MaskingPaint);
  51. return combined;
  52. }
  53. /// <summary>
  54. /// Copies the selection to clipboard in PNG, Bitmap and DIB formats.
  55. /// </summary>
  56. /// <param name="layers">Layers where selection is.</param>
  57. public static void CopyToClipboard(Layer[] layers, Layer selLayer, LayerStructure structure, int originalImageWidth, int originalImageHeight, SerializableDocument document = null)
  58. {
  59. if (!ClipboardHelper.TryClear())
  60. return;
  61. if (layers.Length == 0)
  62. return;
  63. using Surface surface = CreateMaskedCombinedSurface(layers, structure, selLayer);
  64. DataObject data = new DataObject();
  65. //BitmapSource croppedBmp = BitmapSelectionToBmpSource(finalBitmap, selLayer, out int offsetX, out int offsetY, out int width, out int height);
  66. //Remove for now
  67. //data.SetData(typeof(CropData), new CropData(width, height, offsetX, offsetY).ToStream());
  68. using (SKData pngData = surface.SkiaSurface.Snapshot().Encode())
  69. {
  70. // Stream should not be disposed
  71. MemoryStream pngStream = new MemoryStream();
  72. pngData.AsStream().CopyTo(pngStream);
  73. data.SetData("PNG", pngStream, false); // PNG, supports transparency
  74. pngStream.Position = 0;
  75. Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath));
  76. using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
  77. pngStream.CopyTo(fileStream);
  78. data.SetFileDropList(new StringCollection() { TempCopyFilePath });
  79. }
  80. WriteableBitmap finalBitmap = surface.ToWriteableBitmap();
  81. data.SetData(DataFormats.Bitmap, finalBitmap, true); // Bitmap, no transparency
  82. data.SetImage(finalBitmap); // DIB format, no transparency
  83. // Remove pixi copying for now
  84. /*
  85. if (document != null)
  86. {
  87. MemoryStream memoryStream = new();
  88. PixiParser.Serialize(document, memoryStream);
  89. data.SetData("PIXI", memoryStream); // PIXI, supports transparency, layers, groups and swatches
  90. ClipboardHelper.TrySetDataObject(data, true);
  91. }
  92. */
  93. ClipboardHelper.TrySetDataObject(data, true);
  94. }
  95. /// <summary>
  96. /// Pastes image from clipboard into new layer.
  97. /// </summary>
  98. public static void PasteFromClipboard()
  99. {
  100. IEnumerable<Layer> layers;
  101. try
  102. {
  103. layers = GetLayersFromClipboard();
  104. }
  105. catch
  106. {
  107. return;
  108. }
  109. Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
  110. int startIndex = activeDocument.Layers.Count;
  111. foreach (var layer in layers)
  112. {
  113. activeDocument.Layers.Add(layer);
  114. }
  115. activeDocument.UndoManager.AddUndoChange(
  116. new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
  117. }
  118. /// <summary>
  119. /// Gets image from clipboard, supported PNG, Dib and Bitmap.
  120. /// </summary>
  121. private static IEnumerable<Layer> GetLayersFromClipboard()
  122. {
  123. DataObject data = ClipboardHelper.TryGetDataObject();
  124. if (data == null)
  125. yield break;
  126. //Remove pixi for now
  127. /*
  128. if (data.GetDataPresent("PIXI"))
  129. {
  130. SerializableDocument document = GetSerializable(data, out CropData crop);
  131. SKRectI cropRect = SKRectI.Create(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height);
  132. foreach (SerializableLayer sLayer in document)
  133. {
  134. SKRectI intersect;
  135. if (//layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||
  136. !sLayer.IsVisible || sLayer.Opacity == 0 ||
  137. (intersect = SKRectI.Intersect(cropRect, sLayer.GetRect())) == SKRectI.Empty)
  138. {
  139. continue;
  140. }
  141. var layer = sLayer.ToLayer();
  142. layer.Crop(intersect);
  143. yield return layer;
  144. }
  145. }
  146. else */
  147. if (TryFromSingleImage(data, out Surface singleImage))
  148. {
  149. yield return new Layer("Image", singleImage);
  150. }
  151. else if (data.GetDataPresent(DataFormats.FileDrop))
  152. {
  153. foreach (string path in data.GetFileDropList())
  154. {
  155. if (!Importer.IsSupportedFile(path))
  156. {
  157. continue;
  158. }
  159. Layer layer = null;
  160. try
  161. {
  162. layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
  163. }
  164. catch (CorruptedFileException)
  165. {
  166. }
  167. yield return layer ?? new($"Corrupt {path}");
  168. }
  169. }
  170. else
  171. {
  172. yield break;
  173. }
  174. }
  175. public static bool IsImageInClipboard()
  176. {
  177. DataObject dao = ClipboardHelper.TryGetDataObject();
  178. if (dao == null)
  179. return false;
  180. var files = dao.GetFileDropList();
  181. if (files != null)
  182. {
  183. foreach (var file in files)
  184. {
  185. if (Importer.IsSupportedFile(file))
  186. {
  187. return true;
  188. }
  189. }
  190. }
  191. return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
  192. dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
  193. dao.GetDataPresent("PIXI");
  194. }
  195. private static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection, out int offsetX, out int offsetY, out int width, out int height)
  196. {
  197. offsetX = selection.Min(min => min.X);
  198. offsetY = selection.Min(min => min.Y);
  199. width = selection.Max(max => max.X) - offsetX + 1;
  200. height = selection.Max(max => max.Y) - offsetY + 1;
  201. return bitmap.Crop(offsetX, offsetY, width, height);
  202. }
  203. private static BitmapSource FromPNG(DataObject data)
  204. {
  205. MemoryStream pngStream = data.GetData("PNG") as MemoryStream;
  206. PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
  207. return decoder.Frames[0];
  208. }
  209. private static unsafe SerializableDocument GetSerializable(DataObject data, out CropData cropData)
  210. {
  211. MemoryStream pixiStream = data.GetData("PIXI") as MemoryStream;
  212. SerializableDocument document = PixiParser.Deserialize(pixiStream);
  213. if (data.GetDataPresent(typeof(CropData)))
  214. {
  215. cropData = CropData.FromStream(data.GetData(typeof(CropData)) as MemoryStream);
  216. }
  217. else
  218. {
  219. cropData = new CropData(document.Width, document.Height, 0, 0);
  220. }
  221. return document;
  222. }
  223. private static bool TryFromSingleImage(DataObject data, out Surface result)
  224. {
  225. try
  226. {
  227. BitmapSource source;
  228. if (data.GetDataPresent("PNG"))
  229. {
  230. source = FromPNG(data);
  231. }
  232. else if (data.GetDataPresent(DataFormats.Dib) || data.GetDataPresent(DataFormats.Bitmap))
  233. {
  234. source = Clipboard.GetImage();
  235. }
  236. else
  237. {
  238. result = null;
  239. return false;
  240. }
  241. if (source.Format.IsSkiaSupported())
  242. {
  243. result = new Surface(source);
  244. }
  245. else
  246. {
  247. FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
  248. newFormat.BeginInit();
  249. newFormat.Source = source;
  250. newFormat.DestinationFormat = PixelFormats.Bgra32;
  251. newFormat.EndInit();
  252. result = new Surface(newFormat);
  253. }
  254. return true;
  255. }
  256. catch { }
  257. result = null;
  258. return false;
  259. }
  260. private static void RemoveLayersProcess(object[] parameters)
  261. {
  262. if (parameters.Length == 0 || parameters[0] is not int i)
  263. {
  264. return;
  265. }
  266. Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
  267. while (i < document.Layers.Count)
  268. {
  269. document.RemoveLayer(i, false);
  270. }
  271. }
  272. private static void AddLayersProcess(object[] parameters)
  273. {
  274. if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
  275. {
  276. return;
  277. }
  278. foreach (var layer in layers)
  279. {
  280. ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
  281. }
  282. }
  283. private static void DisposeProcess(object[] rev, object[] proc)
  284. {
  285. if (proc[0] is IEnumerable<Layer> layers)
  286. {
  287. foreach (var layer in layers)
  288. {
  289. layer.LayerBitmap.Dispose();
  290. }
  291. }
  292. }
  293. }
  294. }