ClipboardController.cs 13 KB

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