ClipboardController.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. using System.Collections.Generic;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using System.Text.Unicode;
  8. using System.Threading.Tasks;
  9. using Avalonia.Input;
  10. using Avalonia.Input.Platform;
  11. using Avalonia.Media.Imaging;
  12. using Avalonia.Platform;
  13. using Avalonia.Platform.Storage;
  14. using ChunkyImageLib;
  15. using PixiEditor.Helpers.Extensions;
  16. using PixiEditor.ChangeableDocument.Enums;
  17. using Drawie.Backend.Core;
  18. using Drawie.Backend.Core.Numerics;
  19. using Drawie.Backend.Core.Surfaces;
  20. using Drawie.Backend.Core.Surfaces.ImageData;
  21. using Drawie.Backend.Core.Surfaces.PaintImpl;
  22. using PixiEditor.Helpers;
  23. using PixiEditor.Helpers.Constants;
  24. using PixiEditor.Models.Clipboard;
  25. using PixiEditor.Models.Commands.Attributes.Evaluators;
  26. using PixiEditor.Models.Dialogs;
  27. using PixiEditor.Models.IO;
  28. using Drawie.Numerics;
  29. using PixiEditor.ChangeableDocument.Changeables.Animations;
  30. using PixiEditor.Models.Handlers;
  31. using PixiEditor.Parser;
  32. using PixiEditor.UI.Common.Localization;
  33. using PixiEditor.ViewModels.Document;
  34. using PixiEditor.ViewModels.Tools.Tools;
  35. using Bitmap = Avalonia.Media.Imaging.Bitmap;
  36. namespace PixiEditor.Models.Controllers;
  37. #nullable enable
  38. internal static class ClipboardController
  39. {
  40. public static IClipboard Clipboard { get; private set; }
  41. public static void Initialize(IClipboard clipboard)
  42. {
  43. Clipboard = clipboard;
  44. }
  45. public static readonly string TempCopyFilePath = Path.Join(
  46. Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
  47. "PixiEditor",
  48. "Copied.png");
  49. /// <summary>
  50. /// Copies the document elements to clipboard like selection on PNG, Bitmap and DIB formats.
  51. /// Data that is copied:
  52. /// 1. General image data (PNG, Bitmap, DIB), either selection or selected layers of tight bounds size
  53. ///
  54. /// PixiEditor specific stuff:
  55. /// 2. Position of the copied area
  56. /// 3. Layers guid, this is used to duplicate the layer when pasting
  57. /// </summary>
  58. public static async Task CopyToClipboard(DocumentViewModel document, RectD? lastTransform)
  59. {
  60. await Clipboard.ClearAsync();
  61. DataObject data = new DataObject();
  62. Surface surfaceToCopy = null;
  63. RectD copyArea = RectD.Empty;
  64. bool hadSelection = false;
  65. if (!document.SelectionPathBindable.IsEmpty)
  66. {
  67. var surface = document.TryExtractAreaFromSelected((RectI)document.SelectionPathBindable.TightBounds);
  68. if (surface.IsT0)
  69. return;
  70. if (surface.IsT1)
  71. {
  72. NoticeDialog.Show("SELECTED_AREA_EMPTY", "NOTHING_TO_COPY");
  73. return;
  74. }
  75. surfaceToCopy = surface.AsT2.Item1;
  76. copyArea = (RectD)surface.AsT2.Item2;
  77. hadSelection = true;
  78. }
  79. else if (document.TransformViewModel.TransformActive || lastTransform != null)
  80. {
  81. RectD transform = document.TransformViewModel.TransformActive
  82. ? document.TransformViewModel.Corners.AABBBounds
  83. : lastTransform.Value;
  84. var surface =
  85. document.TryExtractAreaFromSelected(
  86. (RectI)transform.RoundOutwards());
  87. if (surface.IsT0 || surface.IsT1)
  88. return;
  89. surfaceToCopy = surface.AsT2.Item1;
  90. copyArea = transform;
  91. }
  92. else if (document.SelectedStructureMember != null)
  93. {
  94. RectI bounds = new RectI(VecI.Zero, document.SizeBindable);
  95. var surface = document.TryExtractAreaFromSelected(bounds);
  96. if (surface.IsT0 || surface.IsT1)
  97. return;
  98. surfaceToCopy = surface.AsT2.Item1;
  99. copyArea = (RectD)bounds;
  100. }
  101. if (surfaceToCopy == null)
  102. {
  103. return;
  104. }
  105. await AddImageToClipboard(surfaceToCopy, data);
  106. if (copyArea.Size != document.SizeBindable && copyArea.Pos != VecI.Zero && copyArea != RectD.Empty)
  107. {
  108. data.SetVecD(ClipboardDataFormats.PositionFormat, copyArea.Pos);
  109. }
  110. if (hadSelection)
  111. {
  112. data.Set(ClipboardDataFormats.HadSelectionFormat, true);
  113. }
  114. string[] layerIds = document.GetSelectedMembers().Select(x => x.ToString()).ToArray();
  115. string layerIdsString = string.Join(";", layerIds);
  116. byte[] layerIdsBytes = Encoding.UTF8.GetBytes(layerIdsString);
  117. data.Set(ClipboardDataFormats.LayerIdList, layerIdsBytes);
  118. data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(document.Id.ToString()));
  119. await Clipboard.SetDataObjectAsync(data);
  120. }
  121. public static async Task CopyVisibleToClipboard(DocumentViewModel document, string? output = null)
  122. {
  123. await Clipboard.ClearAsync();
  124. DataObject data = new DataObject();
  125. RectD copyArea = new RectD(VecD.Zero, document.SizeBindable);
  126. if (!document.SelectionPathBindable.IsEmpty)
  127. {
  128. copyArea = document.SelectionPathBindable.TightBounds;
  129. }
  130. else if (document.TransformViewModel.TransformActive)
  131. {
  132. copyArea = document.TransformViewModel.Corners.AABBBounds;
  133. }
  134. if (copyArea.IsZeroOrNegativeArea || copyArea.HasNaNOrInfinity)
  135. {
  136. NoticeDialog.Show("SELECTED_AREA_EMPTY", "NOTHING_TO_COPY");
  137. return;
  138. }
  139. using Surface documentSurface = new Surface(document.SizeBindable);
  140. document.Renderer.RenderDocument(documentSurface.DrawingSurface,
  141. document.AnimationDataViewModel.ActiveFrameTime, document.SizeBindable, output);
  142. Surface surfaceToCopy = new Surface((VecI)copyArea.Size.Ceiling());
  143. using Paint paint = new Paint();
  144. surfaceToCopy.DrawingSurface.Canvas.DrawImage(
  145. documentSurface.DrawingSurface.Snapshot(),
  146. copyArea, new RectD(0, 0, copyArea.Size.X, copyArea.Size.Y), paint);
  147. await AddImageToClipboard(surfaceToCopy, data);
  148. await Clipboard.SetDataObjectAsync(data);
  149. }
  150. public static async Task<string> GetTextFromClipboard()
  151. {
  152. return await Clipboard.GetTextAsync();
  153. }
  154. private static async Task AddImageToClipboard(Surface actuallySurface, DataObject data)
  155. {
  156. using (ImgData pngData = actuallySurface.DrawingSurface.Snapshot().Encode(EncodedImageFormat.Png))
  157. {
  158. using MemoryStream pngStream = new MemoryStream();
  159. await pngData.AsStream().CopyToAsync(pngStream);
  160. var pngArray = pngStream.ToArray();
  161. foreach (string format in ClipboardDataFormats.PngFormats)
  162. {
  163. if (!data.Contains(format))
  164. {
  165. data.Set(format, pngArray);
  166. }
  167. }
  168. pngStream.Position = 0;
  169. try
  170. {
  171. Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath)!);
  172. await using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
  173. await pngStream.CopyToAsync(fileStream);
  174. }
  175. catch (IOException ioException)
  176. {
  177. string secondaryPath = Path.Combine(
  178. Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
  179. "PixiEditor",
  180. $"Copied_{DateTime.Now:HH-mm-ss}.png");
  181. try
  182. {
  183. Directory.CreateDirectory(Path.GetDirectoryName(secondaryPath)!);
  184. await using FileStream fileStream =
  185. new FileStream(secondaryPath, FileMode.Create, FileAccess.Write);
  186. await pngStream.CopyToAsync(fileStream);
  187. }
  188. catch
  189. {
  190. return;
  191. }
  192. }
  193. data.SetFileDropList(new[] { TempCopyFilePath });
  194. }
  195. }
  196. /// <summary>
  197. /// Pastes image from clipboard into new layer.
  198. /// </summary>
  199. public static async Task<bool> TryPaste(DocumentViewModel document, DocumentManagerViewModel manager,
  200. IImportObject[]
  201. data, bool pasteAsNew = false)
  202. {
  203. Guid sourceDocument = await GetSourceDocument(data, document.Id);
  204. Guid[] layerIds = await GetLayerIds(data);
  205. bool hasPos = data.Any(x => x.Contains(ClipboardDataFormats.PositionFormat));
  206. bool hadSelection = data.Any(x => x.Contains(ClipboardDataFormats.HadSelectionFormat));
  207. IDocument? targetDoc = manager.Documents.FirstOrDefault(x => x.Id == sourceDocument);
  208. if (targetDoc != null && !hadSelection && pasteAsNew && layerIds is { Length: > 0 } &&
  209. (!hasPos || await AllMatchesPos(layerIds, data, targetDoc)))
  210. {
  211. foreach (var layerId in layerIds)
  212. {
  213. if (targetDoc.StructureHelper.Find(layerId) == null)
  214. continue;
  215. if (sourceDocument == document.Id)
  216. {
  217. document.Operations.DuplicateMember(layerId);
  218. }
  219. else
  220. {
  221. document.Operations.ImportMember(layerId, targetDoc);
  222. }
  223. }
  224. manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
  225. return true;
  226. }
  227. List<DataImage> images = await GetImage(data);
  228. if (images.Count == 0)
  229. return false;
  230. if (images.Count == 1 || (images.Count > 1 && !pasteAsNew))
  231. {
  232. var dataImage = images[0];
  233. var position = dataImage.Position;
  234. if (document.SizeBindable.X < position.X || document.SizeBindable.Y < position.Y)
  235. {
  236. position = VecI.Zero;
  237. }
  238. if (pasteAsNew)
  239. {
  240. var guid = document.Operations.CreateStructureMember(StructureMemberType.Layer,
  241. new LocalizedString("NEW_LAYER"), false);
  242. if (guid == null)
  243. {
  244. return false;
  245. }
  246. manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
  247. document.Operations.SetSelectedMember(guid.Value);
  248. document.Operations.PasteImageWithTransform(dataImage.Image, position, guid.Value, false);
  249. }
  250. else
  251. {
  252. manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
  253. document.Operations.PasteImageWithTransform(dataImage.Image, position);
  254. }
  255. return true;
  256. }
  257. document.Operations.PasteImagesAsLayers(images, document.AnimationDataViewModel.ActiveFrameBindable);
  258. return true;
  259. }
  260. private static async Task<bool> AllMatchesPos(Guid[] layerIds, IImportObject[] dataFormats, IDocument doc)
  261. {
  262. var dataObjectWithPos = dataFormats.FirstOrDefault(x => x.Contains(ClipboardDataFormats.PositionFormat));
  263. VecD pos = VecD.Zero;
  264. if (dataObjectWithPos != null)
  265. {
  266. pos = await GetVecD(ClipboardDataFormats.PositionFormat, dataFormats);
  267. }
  268. RectD? tightBounds = null;
  269. for (var i = 0; i < layerIds.Length; i++)
  270. {
  271. var layerId = layerIds[i];
  272. var layer = doc.StructureHelper.Find(layerId);
  273. if (layer == null) return false;
  274. if (tightBounds == null)
  275. {
  276. tightBounds = layer.TightBounds;
  277. }
  278. else if (layer.TightBounds.HasValue)
  279. {
  280. tightBounds = tightBounds.Value.Union(layer.TightBounds.Value);
  281. }
  282. }
  283. return tightBounds.HasValue && tightBounds.Value.Pos.AlmostEquals(pos);
  284. }
  285. private static async Task<Guid[]> GetLayerIds(IImportObject[] formats)
  286. {
  287. foreach (var dataObject in formats)
  288. {
  289. if (dataObject.Contains(ClipboardDataFormats.LayerIdList))
  290. {
  291. byte[] layerIds = await Clipboard.GetDataAsync(ClipboardDataFormats.LayerIdList) as byte[];
  292. string layerIdsString = System.Text.Encoding.UTF8.GetString(layerIds);
  293. return layerIdsString.Split(';').Select(Guid.Parse).ToArray();
  294. }
  295. }
  296. return [];
  297. }
  298. private static async Task<Guid> GetSourceDocument(IImportObject[] formats, Guid fallback)
  299. {
  300. foreach (var dataObject in formats)
  301. {
  302. if (dataObject.Contains(ClipboardDataFormats.DocumentFormat))
  303. {
  304. var data = await Clipboard.GetDataAsync(ClipboardDataFormats.DocumentFormat);
  305. byte[] guidBytes = (byte[])data;
  306. string guidString = System.Text.Encoding.UTF8.GetString(guidBytes);
  307. return Guid.Parse(guidString);
  308. }
  309. }
  310. return fallback;
  311. }
  312. /// <summary>
  313. /// Pastes image from clipboard into new layer.
  314. /// </summary>
  315. public static async Task<bool> TryPasteFromClipboard(DocumentViewModel document, DocumentManagerViewModel manager,
  316. bool pasteAsNew = false)
  317. {
  318. var data = await TryGetImportObjects();
  319. if (data == null)
  320. return false;
  321. return await TryPaste(document, manager, data, pasteAsNew);
  322. }
  323. private static async Task<ClipboardPromiseObject[]> TryGetImportObjects()
  324. {
  325. string[] formats = await Clipboard.GetFormatsAsync();
  326. if (formats.Length == 0)
  327. return null;
  328. List<ClipboardPromiseObject?> dataObjects = new();
  329. for (int i = 0; i < formats.Length; i++)
  330. {
  331. string format = formats[i];
  332. dataObjects.Add(new ClipboardPromiseObject(format, Clipboard));
  333. }
  334. return dataObjects.ToArray();
  335. }
  336. public static async Task<List<DataImage>> GetImagesFromClipboard()
  337. {
  338. var dataObj = await TryGetImportObjects();
  339. return await GetImage(dataObj);
  340. }
  341. /// <summary>
  342. /// Gets images from clipboard, supported PNG and Bitmap.
  343. /// </summary>
  344. public static async Task<List<DataImage>> GetImage(IImportObject[] importableObjects)
  345. {
  346. List<DataImage> surfaces = new();
  347. if (importableObjects == null)
  348. return surfaces;
  349. VecD pos = VecD.Zero;
  350. string? importingType = null;
  351. bool pngImported = false;
  352. foreach (var dataObject in importableObjects)
  353. {
  354. var img = await TryExtractSingleImage(dataObject);
  355. if (importingType is null or "bytes" && img != null && !pngImported)
  356. {
  357. surfaces.Add(new DataImage(img,
  358. dataObject.Contains(ClipboardDataFormats.PositionFormat)
  359. ? (VecI)await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects)
  360. : (VecI)pos));
  361. importingType = "bytes";
  362. pngImported = true;
  363. continue;
  364. }
  365. if (dataObject.Contains(ClipboardDataFormats.PositionFormat))
  366. {
  367. pos = await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects);
  368. for (var i = 0; i < surfaces.Count; i++)
  369. {
  370. var surface = surfaces[i];
  371. surfaces[i] = surface with { Position = (VecI)pos };
  372. }
  373. }
  374. var paths = (await GetFileDropList(dataObject))?.Select(x => x.Path.LocalPath).ToList();
  375. string[]? rawPaths = await TryGetRawTextPaths(dataObject);
  376. if (paths != null && rawPaths != null)
  377. {
  378. paths.AddRange(rawPaths);
  379. }
  380. if (paths == null || paths.Count == 0 || (importingType != null && importingType != "files"))
  381. {
  382. continue;
  383. }
  384. foreach (string? path in paths)
  385. {
  386. if (path is null || !Importer.IsSupportedFile(path))
  387. continue;
  388. try
  389. {
  390. Surface imported;
  391. if (Path.GetExtension(path) == ".pixi")
  392. {
  393. using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
  394. imported = Surface.Load(PixiParser.ReadPreview(stream));
  395. }
  396. else
  397. {
  398. imported = Surface.Load(path);
  399. }
  400. string filename = Path.GetFullPath(path);
  401. surfaces.Add(new DataImage(filename, imported,
  402. (VecI)await GetVecD(ClipboardDataFormats.PositionFormat, importableObjects)));
  403. importingType = "files";
  404. }
  405. catch
  406. {
  407. continue;
  408. }
  409. }
  410. }
  411. return surfaces;
  412. }
  413. private static async Task<string[]?> TryGetRawTextPaths(IImportObject importObj)
  414. {
  415. if (!importObj.Contains(DataFormats.Text) && !importObj.Contains(ClipboardDataFormats.UriList))
  416. {
  417. return null;
  418. }
  419. string text = null;
  420. try
  421. {
  422. text = await importObj.GetDataAsync(DataFormats.Text) as string;
  423. }
  424. catch (InvalidCastException ex) // bug on x11
  425. {
  426. }
  427. string[] paths = [text];
  428. if (text == null)
  429. {
  430. if (await importObj.GetDataAsync(ClipboardDataFormats.UriList) is byte[] bytes)
  431. {
  432. paths = Encoding.UTF8.GetString(bytes).Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
  433. }
  434. }
  435. if (paths.Length == 0)
  436. {
  437. return null;
  438. }
  439. List<string> validPaths = new();
  440. foreach (string path in paths)
  441. {
  442. if (string.IsNullOrWhiteSpace(path))
  443. continue;
  444. if (Directory.Exists(path) || File.Exists(path))
  445. {
  446. validPaths.Add(path);
  447. }
  448. else
  449. {
  450. try
  451. {
  452. Uri uri = new Uri(path);
  453. if (uri.IsAbsoluteUri && (Directory.Exists(uri.LocalPath) || File.Exists(uri.LocalPath)))
  454. {
  455. validPaths.Add(uri.LocalPath);
  456. }
  457. }
  458. catch (UriFormatException)
  459. {
  460. // Ignore invalid URIs
  461. }
  462. }
  463. }
  464. return validPaths.Count > 0 ? validPaths.ToArray() : null;
  465. }
  466. private static async Task<IEnumerable<IStorageItem>> GetFileDropList(IImportObject obj)
  467. {
  468. if (!obj.Contains(DataFormats.Files))
  469. return [];
  470. var data = await obj.GetDataAsync(DataFormats.Files);
  471. if (data == null)
  472. return [];
  473. if (data is IEnumerable<IStorageItem> storageItems)
  474. return storageItems;
  475. if (data is Task<object> task)
  476. {
  477. data = await task;
  478. if (data is IEnumerable<IStorageItem> storageItemsFromTask)
  479. return storageItemsFromTask;
  480. }
  481. return [];
  482. }
  483. private static async Task<VecD> GetVecD(string format, IImportObject[] availableFormats)
  484. {
  485. var firstFormat = availableFormats.FirstOrDefault(x => x.Contains(format));
  486. if (firstFormat == null)
  487. return new VecD(-1, -1);
  488. byte[] bytes = (byte[])await firstFormat.GetDataAsync(format);
  489. if (bytes is { Length: < 16 })
  490. return new VecD(-1, -1);
  491. return VecD.FromBytes(bytes);
  492. }
  493. public static bool IsImage(IDataObject? dataObject)
  494. {
  495. if (dataObject == null)
  496. return false;
  497. try
  498. {
  499. var files = dataObject.GetFileDropList();
  500. if (files != null)
  501. {
  502. if (IsImageFormat(files.Select(x => x.Path.LocalPath).ToArray()))
  503. {
  504. return true;
  505. }
  506. }
  507. }
  508. catch (COMException)
  509. {
  510. return false;
  511. }
  512. return HasData(dataObject, ClipboardDataFormats.PngFormats);
  513. }
  514. public static async Task<bool> IsImageInClipboard()
  515. {
  516. var formats = await Clipboard.GetFormatsAsync();
  517. if (formats == null || formats.Length == 0)
  518. return false;
  519. bool isImage = IsImageFormat(formats);
  520. if (!isImage)
  521. {
  522. string path = await TryFindImageInFiles(formats);
  523. try
  524. {
  525. Uri uri = new Uri(path);
  526. return Path.Exists(uri.LocalPath);
  527. }
  528. catch (UriFormatException)
  529. {
  530. return false;
  531. }
  532. catch (Exception ex)
  533. {
  534. CrashHelper.SendExceptionInfo(ex);
  535. return false;
  536. }
  537. }
  538. return isImage;
  539. }
  540. private static async Task<string> TryFindImageInFiles(string[] formats)
  541. {
  542. foreach (string format in formats)
  543. {
  544. if (format == DataFormats.Files || format == ClipboardDataFormats.UriList)
  545. {
  546. var files = await ClipboardController.Clipboard.GetDataAsync(format);
  547. if (files is IEnumerable<IStorageItem> storageFiles)
  548. {
  549. foreach (var file in storageFiles)
  550. {
  551. try
  552. {
  553. if (Importer.IsSupportedFile(file.Path.LocalPath))
  554. {
  555. return file.Path.LocalPath;
  556. }
  557. }
  558. catch (UriFormatException)
  559. {
  560. continue;
  561. }
  562. }
  563. }
  564. if (files is byte[] bytes)
  565. {
  566. string utf8String = Encoding.UTF8.GetString(bytes);
  567. string[] paths = utf8String.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
  568. foreach (string path in paths)
  569. {
  570. if (Importer.IsSupportedFile(path))
  571. {
  572. return path;
  573. }
  574. }
  575. }
  576. if (format == DataFormats.Text)
  577. {
  578. string text = await ClipboardController.GetTextFromClipboard();
  579. if (Importer.IsSupportedFile(text))
  580. {
  581. return text;
  582. }
  583. }
  584. }
  585. }
  586. return string.Empty;
  587. }
  588. public static bool IsImageFormat(string[] formats)
  589. {
  590. foreach (var format in formats)
  591. {
  592. if (ClipboardDataFormats.PngFormats.Contains(format, StringComparer.OrdinalIgnoreCase))
  593. {
  594. return true;
  595. }
  596. if (Importer.IsSupportedFile(format))
  597. {
  598. return true;
  599. }
  600. }
  601. return false;
  602. }
  603. private static async Task<Surface?> FromPNG(IImportObject importObj)
  604. {
  605. object? pngData = null;
  606. foreach (string format in ClipboardDataFormats.PngFormats)
  607. {
  608. if (importObj.Contains(format))
  609. {
  610. object? data = await importObj.GetDataAsync(format);
  611. if (data == null)
  612. continue;
  613. pngData = data;
  614. break;
  615. }
  616. }
  617. if (pngData is byte[] bytes)
  618. {
  619. return Surface.Load(bytes);
  620. }
  621. if (pngData is MemoryStream memoryStream)
  622. {
  623. bytes = memoryStream.ToArray();
  624. return Surface.Load(bytes);
  625. }
  626. return null;
  627. }
  628. private static bool HasData(IDataObject dataObject, params string[] formats) => formats.Any(dataObject.Contains);
  629. private static async Task<Surface?> TryExtractSingleImage(IImportObject importedObj)
  630. {
  631. try
  632. {
  633. Surface source;
  634. bool dataContainsPng = ClipboardDataFormats.PngFormats.Any(importedObj.Contains);
  635. if (dataContainsPng)
  636. {
  637. source = await FromPNG(importedObj);
  638. if (source == null)
  639. {
  640. return null;
  641. }
  642. }
  643. else
  644. {
  645. return null;
  646. }
  647. /*if (source.Format.Value.IsSkiaSupported())
  648. {
  649. result = SurfaceHelpers.FromBitmap(source);
  650. }
  651. else
  652. {
  653. source.ExtractPixels(out IntPtr address);
  654. Bitmap newFormat = new Bitmap(PixelFormats.Bgra8888, AlphaFormat.Premul, address, source.PixelSize,
  655. source.Dpi, source.PixelSize.Width * 4);
  656. result = SurfaceHelpers.FromBitmap(newFormat);
  657. }*/
  658. return source;
  659. }
  660. catch { }
  661. return null;
  662. }
  663. public static async Task CopyNodes(Guid[] nodeIds, Guid docId)
  664. {
  665. await CopyIds(nodeIds, ClipboardDataFormats.NodeIdList, docId);
  666. }
  667. public static async Task<Guid[]> GetNodeIds()
  668. {
  669. return await GetIds(ClipboardDataFormats.NodeIdList);
  670. }
  671. public static async Task<Guid[]> GetCelIds()
  672. {
  673. return await GetIds(ClipboardDataFormats.CelIdList);
  674. }
  675. public static async Task<Guid[]> GetIds(string format)
  676. {
  677. var data = await TryGetImportObjects();
  678. return await GetIds(data, format);
  679. }
  680. private static async Task<Guid[]> GetIds(IEnumerable<IImportObject?> data, string format)
  681. {
  682. foreach (var dataObject in data)
  683. {
  684. if (dataObject.Contains(format))
  685. {
  686. byte[] nodeIds = await dataObject.GetDataAsync(format) as byte[];
  687. if (nodeIds == null || nodeIds.Length == 0)
  688. return [];
  689. string nodeIdsString = System.Text.Encoding.UTF8.GetString(nodeIds);
  690. return nodeIdsString.Split(';').Select(Guid.Parse).ToArray();
  691. }
  692. }
  693. return [];
  694. }
  695. public static async Task<bool> AreNodesInClipboard()
  696. {
  697. return await AreIdsInClipboard(ClipboardDataFormats.NodeIdList);
  698. }
  699. public static async Task<bool> AreCelsInClipboard()
  700. {
  701. return await AreIdsInClipboard(ClipboardDataFormats.CelIdList);
  702. }
  703. public static async Task<bool> AreIdsInClipboard(string format)
  704. {
  705. var formats = await Clipboard.GetFormatsAsync();
  706. if (formats == null || formats.Length == 0)
  707. return false;
  708. return formats.Contains(format);
  709. }
  710. public static async Task CopyCels(Guid[] celIds, Guid docId)
  711. {
  712. await CopyIds(celIds, ClipboardDataFormats.CelIdList, docId);
  713. }
  714. public static async Task CopyIds(Guid[] ids, string format, Guid docId)
  715. {
  716. await Clipboard.ClearAsync();
  717. DataObject data = new DataObject();
  718. data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(docId.ToString()));
  719. byte[] idsBytes = Encoding.UTF8.GetBytes(string.Join(";", ids.Select(x => x.ToString())));
  720. data.Set(format, idsBytes);
  721. await Clipboard.SetDataObjectAsync(data);
  722. }
  723. public static async Task<Guid> GetDocumentId()
  724. {
  725. var data = await TryGetImportObjects();
  726. if (data == null)
  727. return Guid.Empty;
  728. foreach (var dataObject in data)
  729. {
  730. if (dataObject.Contains(ClipboardDataFormats.DocumentFormat))
  731. {
  732. byte[] guidBytes = (byte[])await dataObject.GetDataAsync(ClipboardDataFormats.DocumentFormat);
  733. string guidString = System.Text.Encoding.UTF8.GetString(guidBytes);
  734. return Guid.Parse(guidString);
  735. }
  736. }
  737. return Guid.Empty;
  738. }
  739. }