ClipboardController.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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. 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. using Surface surface = CreateMaskedCombinedSurface(layers, structure, selLayer);
  62. DataObject data = new DataObject();
  63. //BitmapSource croppedBmp = BitmapSelectionToBmpSource(finalBitmap, selLayer, out int offsetX, out int offsetY, out int width, out int height);
  64. //Remove for now
  65. //data.SetData(typeof(CropData), new CropData(width, height, offsetX, offsetY).ToStream());
  66. using (SKData pngData = surface.SkiaSurface.Snapshot().Encode())
  67. {
  68. // Stream should not be disposed
  69. MemoryStream pngStream = new MemoryStream();
  70. pngData.AsStream().CopyTo(pngStream);
  71. data.SetData("PNG", pngStream, false); // PNG, supports transparency
  72. pngStream.Position = 0;
  73. Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath));
  74. using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
  75. pngStream.CopyTo(fileStream);
  76. data.SetFileDropList(new StringCollection() { TempCopyFilePath });
  77. }
  78. WriteableBitmap finalBitmap = surface.ToWriteableBitmap();
  79. data.SetData(DataFormats.Bitmap, finalBitmap, true); // Bitmap, no transparency
  80. data.SetImage(finalBitmap); // DIB format, no transparency
  81. // Remove pixi copying for now
  82. /*
  83. if (document != null)
  84. {
  85. MemoryStream memoryStream = new();
  86. PixiParser.Serialize(document, memoryStream);
  87. data.SetData("PIXI", memoryStream); // PIXI, supports transparency, layers, groups and swatches
  88. ClipboardHelper.TrySetDataObject(data, true);
  89. }
  90. */
  91. ClipboardHelper.TrySetDataObject(data, true);
  92. }
  93. /// <summary>
  94. /// Pastes image from clipboard into new layer.
  95. /// </summary>
  96. public static void PasteFromClipboard()
  97. {
  98. IEnumerable<Layer> layers;
  99. try
  100. {
  101. layers = GetLayersFromClipboard();
  102. }
  103. catch
  104. {
  105. return;
  106. }
  107. Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
  108. int startIndex = activeDocument.Layers.Count;
  109. foreach (var layer in layers)
  110. {
  111. activeDocument.Layers.Add(layer);
  112. }
  113. activeDocument.UndoManager.AddUndoChange(
  114. new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
  115. }
  116. /// <summary>
  117. /// Gets image from clipboard, supported PNG, Dib and Bitmap.
  118. /// </summary>
  119. /// <returns>WriteableBitmap.</returns>
  120. private static IEnumerable<Layer> GetLayersFromClipboard()
  121. {
  122. DataObject data = ClipboardHelper.TryGetDataObject();
  123. if (data == null)
  124. yield break;
  125. //Remove pixi for now
  126. /*
  127. if (data.GetDataPresent("PIXI"))
  128. {
  129. SerializableDocument document = GetSerializable(data, out CropData crop);
  130. SKRectI cropRect = SKRectI.Create(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height);
  131. foreach (SerializableLayer sLayer in document)
  132. {
  133. SKRectI intersect;
  134. if (//layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||
  135. !sLayer.IsVisible || sLayer.Opacity == 0 ||
  136. (intersect = SKRectI.Intersect(cropRect, sLayer.GetRect())) == SKRectI.Empty)
  137. {
  138. continue;
  139. }
  140. var layer = sLayer.ToLayer();
  141. layer.Crop(intersect);
  142. yield return layer;
  143. }
  144. }
  145. else */
  146. if (TryFromSingleImage(data, out Surface singleImage))
  147. {
  148. yield return new Layer("Image", singleImage);
  149. }
  150. else if (data.GetDataPresent(DataFormats.FileDrop))
  151. {
  152. foreach (string path in data.GetFileDropList())
  153. {
  154. if (!Importer.IsSupportedFile(path))
  155. {
  156. continue;
  157. }
  158. Layer layer = null;
  159. try
  160. {
  161. layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
  162. }
  163. catch (CorruptedFileException)
  164. {
  165. }
  166. yield return layer ?? new($"Corrupt {path}");
  167. }
  168. }
  169. else
  170. {
  171. yield break;
  172. }
  173. }
  174. public static bool IsImageInClipboard()
  175. {
  176. DataObject dao = ClipboardHelper.TryGetDataObject();
  177. if (dao == null)
  178. return false;
  179. var files = dao.GetFileDropList();
  180. if (files != null)
  181. {
  182. foreach (var file in files)
  183. {
  184. if (Importer.IsSupportedFile(file))
  185. {
  186. return true;
  187. }
  188. }
  189. }
  190. return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
  191. dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
  192. dao.GetDataPresent("PIXI");
  193. }
  194. private static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection, out int offsetX, out int offsetY, out int width, out int height)
  195. {
  196. offsetX = selection.Min(min => min.X);
  197. offsetY = selection.Min(min => min.Y);
  198. width = selection.Max(max => max.X) - offsetX + 1;
  199. height = selection.Max(max => max.Y) - offsetY + 1;
  200. return bitmap.Crop(offsetX, offsetY, width, height);
  201. }
  202. private static BitmapSource FromPNG(DataObject data)
  203. {
  204. MemoryStream pngStream = data.GetData("PNG") as MemoryStream;
  205. PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
  206. return decoder.Frames[0];
  207. }
  208. private static unsafe SerializableDocument GetSerializable(DataObject data, out CropData cropData)
  209. {
  210. MemoryStream pixiStream = data.GetData("PIXI") as MemoryStream;
  211. SerializableDocument document = PixiParser.Deserialize(pixiStream);
  212. if (data.GetDataPresent(typeof(CropData)))
  213. {
  214. cropData = CropData.FromStream(data.GetData(typeof(CropData)) as MemoryStream);
  215. }
  216. else
  217. {
  218. cropData = new CropData(document.Width, document.Height, 0, 0);
  219. }
  220. return document;
  221. }
  222. private static bool TryFromSingleImage(DataObject data, out Surface result)
  223. {
  224. try
  225. {
  226. BitmapSource source;
  227. if (data.GetDataPresent("PNG"))
  228. {
  229. source = FromPNG(data);
  230. }
  231. else if (data.GetDataPresent(DataFormats.Dib) || data.GetDataPresent(DataFormats.Bitmap))
  232. {
  233. source = Clipboard.GetImage();
  234. }
  235. else
  236. {
  237. result = null;
  238. return false;
  239. }
  240. if (source.Format.IsSkiaSupported())
  241. {
  242. result = new Surface(source);
  243. }
  244. else
  245. {
  246. FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
  247. newFormat.BeginInit();
  248. newFormat.Source = source;
  249. newFormat.DestinationFormat = PixelFormats.Rgba64;
  250. newFormat.EndInit();
  251. result = new Surface(newFormat);
  252. }
  253. return true;
  254. }
  255. catch { }
  256. result = null;
  257. return false;
  258. }
  259. private static void RemoveLayersProcess(object[] parameters)
  260. {
  261. if (parameters.Length == 0 || parameters[0] is not int i)
  262. {
  263. return;
  264. }
  265. Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
  266. while (i < document.Layers.Count)
  267. {
  268. document.RemoveLayer(i, true);
  269. }
  270. }
  271. private static void AddLayersProcess(object[] parameters)
  272. {
  273. if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
  274. {
  275. return;
  276. }
  277. foreach (var layer in layers)
  278. {
  279. ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
  280. }
  281. }
  282. private static void DisposeProcess(object[] rev, object[] proc)
  283. {
  284. if (proc[0] is IEnumerable<Layer> layers)
  285. {
  286. foreach (var layer in layers)
  287. {
  288. layer.LayerBitmap.Dispose();
  289. }
  290. }
  291. }
  292. }
  293. }