DocumentViewModel.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.Collections.ObjectModel;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.Json;
  8. using Avalonia;
  9. using Avalonia.Media.Imaging;
  10. using Avalonia.Threading;
  11. using ChunkyImageLib;
  12. using ChunkyImageLib.DataHolders;
  13. using ChunkyImageLib.Operations;
  14. using Microsoft.CodeAnalysis.CSharp.Syntax;
  15. using Microsoft.Extensions.DependencyInjection;
  16. using PixiEditor.Models.DocumentPassthroughActions;
  17. using PixiEditor.Models.Position;
  18. using PixiEditor.ViewModels.SubViewModels;
  19. using PixiEditor.ChangeableDocument.Actions;
  20. using PixiEditor.ChangeableDocument.Actions.Generated;
  21. using PixiEditor.ChangeableDocument.Actions.Undo;
  22. using PixiEditor.ChangeableDocument.Changeables.Animations;
  23. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  24. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  25. using PixiEditor.ChangeableDocument.Changeables.Interfaces;
  26. using PixiEditor.ChangeableDocument.ChangeInfos;
  27. using PixiEditor.ChangeableDocument.Changes.NodeGraph;
  28. using PixiEditor.ChangeableDocument.Enums;
  29. using PixiEditor.ChangeableDocument.Rendering;
  30. using Drawie.Backend.Core;
  31. using Drawie.Backend.Core.Bridge;
  32. using Drawie.Backend.Core.Numerics;
  33. using Drawie.Backend.Core.Surfaces.ImageData;
  34. using Drawie.Backend.Core.Surfaces.PaintImpl;
  35. using Drawie.Backend.Core.Vector;
  36. using PixiEditor.Extensions.Common.Localization;
  37. using PixiEditor.Extensions.CommonApi.Palettes;
  38. using PixiEditor.Helpers;
  39. using PixiEditor.Helpers.Collections;
  40. using PixiEditor.Helpers.Extensions;
  41. using PixiEditor.Models.Controllers;
  42. using PixiEditor.Models.DocumentModels;
  43. using PixiEditor.Models.DocumentModels.Public;
  44. using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
  45. using PixiEditor.Models.Handlers;
  46. using PixiEditor.Models.Layers;
  47. using PixiEditor.Models.Rendering;
  48. using PixiEditor.Models.Serialization;
  49. using PixiEditor.Models.Serialization.Factories;
  50. using PixiEditor.Models.Structures;
  51. using PixiEditor.Models.Tools;
  52. using Drawie.Numerics;
  53. using PixiEditor.Models.IO;
  54. using PixiEditor.Parser;
  55. using PixiEditor.Parser.Skia;
  56. using PixiEditor.ViewModels.Document.Nodes;
  57. using PixiEditor.ViewModels.Document.TransformOverlays;
  58. using PixiEditor.Views.Overlays.SymmetryOverlay;
  59. using BlendMode = Drawie.Backend.Core.Surfaces.BlendMode;
  60. using Color = Drawie.Backend.Core.ColorsImpl.Color;
  61. using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
  62. using Node = PixiEditor.Parser.Graph.Node;
  63. using Point = Avalonia.Point;
  64. namespace PixiEditor.ViewModels.Document;
  65. #nullable enable
  66. internal partial class DocumentViewModel : PixiObservableObject, IDocument
  67. {
  68. public event EventHandler<LayersChangedEventArgs>? LayersChanged;
  69. public event EventHandler<DocumentSizeChangedEventArgs>? SizeChanged;
  70. public event Action ToolSessionFinished;
  71. private bool busy = false;
  72. public bool Busy
  73. {
  74. get => busy;
  75. set => SetProperty(ref busy, value);
  76. }
  77. private string coordinatesString = "";
  78. public string CoordinatesString
  79. {
  80. get => coordinatesString;
  81. set => SetProperty(ref coordinatesString, value);
  82. }
  83. private string? fullFilePath = null;
  84. public string? FullFilePath
  85. {
  86. get => fullFilePath;
  87. set
  88. {
  89. SetProperty(ref fullFilePath, value);
  90. OnPropertyChanged(nameof(FileName));
  91. }
  92. }
  93. public string FileName
  94. {
  95. get => fullFilePath is null ? new LocalizedString("UNNAMED") : Path.GetFileName(fullFilePath);
  96. }
  97. private Guid? lastChangeOnSave = null;
  98. private Guid? lastChangeOnAutosave = null;
  99. public bool AllChangesSaved
  100. {
  101. get
  102. {
  103. return Internals.Tracker.LastChangeGuid == lastChangeOnSave;
  104. }
  105. }
  106. public bool AllChangesAutosaved
  107. {
  108. get
  109. {
  110. return Internals.Tracker.LastChangeGuid == lastChangeOnAutosave;
  111. }
  112. }
  113. public DateTime OpenedUTC { get; } = DateTime.UtcNow;
  114. private bool horizontalSymmetryAxisEnabled;
  115. public bool HorizontalSymmetryAxisEnabledBindable
  116. {
  117. get => horizontalSymmetryAxisEnabled;
  118. set
  119. {
  120. if (!Internals.ChangeController.IsBlockingChangeActive)
  121. Internals.ActionAccumulator.AddFinishedActions(
  122. new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
  123. }
  124. }
  125. private bool verticalSymmetryAxisEnabled;
  126. public bool VerticalSymmetryAxisEnabledBindable
  127. {
  128. get => verticalSymmetryAxisEnabled;
  129. set
  130. {
  131. if (!Internals.ChangeController.IsBlockingChangeActive)
  132. Internals.ActionAccumulator.AddFinishedActions(
  133. new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
  134. }
  135. }
  136. public bool AnySymmetryAxisEnabledBindable =>
  137. HorizontalSymmetryAxisEnabledBindable || VerticalSymmetryAxisEnabledBindable;
  138. private VecI size = new VecI(64, 64);
  139. public int Width => size.X;
  140. public int Height => size.Y;
  141. public VecI SizeBindable => size;
  142. private double horizontalSymmetryAxisY;
  143. public double HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
  144. private double verticalSymmetryAxisX;
  145. public double VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
  146. private readonly HashSet<IStructureMemberHandler> softSelectedStructureMembers = new();
  147. public bool BlockingUpdateableChangeActive => Internals.ChangeController.IsBlockingChangeActive;
  148. public bool IsChangeFeatureActive<T>() where T : IExecutorFeature =>
  149. Internals.ChangeController.IsChangeOfTypeActive<T>();
  150. public T? TryGetExecutorFeature<T>() where T : IExecutorFeature =>
  151. Internals.ChangeController.TryGetExecutorFeature<T>();
  152. public bool PointerDragChangeInProgress =>
  153. Internals.ChangeController.IsBlockingChangeActive && Internals.ChangeController.LeftMousePressed;
  154. public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
  155. public bool HasSavedRedo => Internals.Tracker.HasSavedRedo;
  156. public NodeGraphViewModel NodeGraph { get; }
  157. public DocumentStructureModule StructureHelper { get; }
  158. public DocumentToolsModule Tools { get; }
  159. public DocumentOperationsModule Operations { get; }
  160. public DocumentRenderer Renderer { get; }
  161. public SceneRenderer SceneRenderer { get; }
  162. public DocumentEventsModule EventInlet { get; }
  163. public ActionDisplayList ActionDisplays { get; } =
  164. new(() => ViewModelMain.Current.NotifyToolActionDisplayChanged());
  165. public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
  166. private PreviewPainter previewSurface;
  167. public PreviewPainter PreviewPainter
  168. {
  169. get => previewSurface;
  170. set
  171. {
  172. SetProperty(ref previewSurface, value);
  173. }
  174. }
  175. private VectorPath selectionPath = new VectorPath();
  176. public VectorPath SelectionPathBindable => selectionPath;
  177. public ObservableCollection<PaletteColor> Swatches { get; set; } = new();
  178. public Guid Id => Internals.Tracker.Document.DocumentId;
  179. public ObservableRangeCollection<PaletteColor> Palette { get; set; } = new();
  180. public SnappingViewModel SnappingViewModel { get; }
  181. ISnappingHandler IDocument.SnappingHandler => SnappingViewModel;
  182. public IReadOnlyCollection<Guid> SelectedMembers => GetSelectedMembers().AsReadOnly();
  183. public DocumentTransformViewModel TransformViewModel { get; }
  184. public PathOverlayViewModel PathOverlayViewModel { get; }
  185. public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
  186. public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
  187. public AnimationDataViewModel AnimationDataViewModel { get; }
  188. public TextOverlayViewModel TextOverlayViewModel { get; }
  189. public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
  190. private DocumentInternalParts Internals { get; }
  191. INodeGraphHandler IDocument.NodeGraphHandler => NodeGraph;
  192. IDocumentOperations IDocument.Operations => Operations;
  193. ITransformHandler IDocument.TransformHandler => TransformViewModel;
  194. ITextOverlayHandler IDocument.TextOverlayHandler => TextOverlayViewModel;
  195. IPathOverlayHandler IDocument.PathOverlayHandler => PathOverlayViewModel;
  196. ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
  197. IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
  198. IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
  199. public bool UsesSrgbBlending { get; private set; }
  200. public AutosaveDocumentViewModel AutosaveViewModel { get; }
  201. private DocumentViewModel()
  202. {
  203. var serviceProvider = ViewModelMain.Current.Services;
  204. Internals = new DocumentInternalParts(this, serviceProvider);
  205. Internals.ChangeController.ToolSessionFinished += () => ToolSessionFinished?.Invoke();
  206. Tools = new DocumentToolsModule(this, Internals);
  207. StructureHelper = new DocumentStructureModule(this);
  208. EventInlet = new DocumentEventsModule(this, Internals);
  209. Operations = new DocumentOperationsModule(this, Internals);
  210. AnimationDataViewModel = new(this, Internals);
  211. NodeGraph = new NodeGraphViewModel(this, Internals);
  212. AutosaveViewModel = new AutosaveDocumentViewModel(this, Internals);
  213. TransformViewModel = new(this);
  214. TransformViewModel.TransformChanged += (args) => Internals.ChangeController.TransformChangedInlet(args);
  215. TransformViewModel.TransformDragged += (from, to) => Internals.ChangeController.TransformDraggedInlet(from, to);
  216. TransformViewModel.TransformStopped += () => Internals.ChangeController.TransformStoppedInlet();
  217. PathOverlayViewModel = new(this, Internals);
  218. PathOverlayViewModel.PathChanged += path =>
  219. {
  220. Internals.ChangeController.PathOverlayChangedInlet(path);
  221. };
  222. LineToolOverlayViewModel = new();
  223. LineToolOverlayViewModel.LineMoved += (_, args) =>
  224. Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
  225. TextOverlayViewModel = new TextOverlayViewModel();
  226. TextOverlayViewModel.TextChanged += text =>
  227. {
  228. Internals.ChangeController.TextOverlayTextChangedInlet(text);
  229. };
  230. SnappingViewModel = new();
  231. SnappingViewModel.AddFromDocumentSize(SizeBindable);
  232. SizeChanged += (_, args) =>
  233. {
  234. SnappingViewModel.AddFromDocumentSize(args.NewSize);
  235. };
  236. LayersChanged += (sender, args) =>
  237. {
  238. if (args.LayerChangeType == LayerAction.Add)
  239. {
  240. IReadOnlyStructureNode layer = Internals.Tracker.Document.FindMember(args.LayerAffectedGuid);
  241. if (layer is not null)
  242. {
  243. SnappingViewModel.AddFromBounds(layer.Id.ToString(),
  244. () => layer.GetTightBounds(AnimationDataViewModel.ActiveFrameTime) ?? RectD.Empty);
  245. }
  246. }
  247. else if (args.LayerChangeType == LayerAction.Remove)
  248. {
  249. SnappingViewModel.Remove(args.LayerAffectedGuid.ToString());
  250. }
  251. };
  252. ReferenceLayerViewModel = new(this, Internals);
  253. Renderer = new DocumentRenderer(Internals.Tracker.Document);
  254. SceneRenderer = new SceneRenderer(Internals.Tracker.Document, this);
  255. }
  256. /// <summary>
  257. /// Creates a new document using the <paramref name="builder"/>
  258. /// </summary>
  259. /// <returns>The created document</returns>
  260. public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
  261. {
  262. var builderInstance = new DocumentViewModelBuilder();
  263. builder(builderInstance);
  264. (string serializerName, string serializerVersion) serializerData = (builderInstance.SerializerName,
  265. builderInstance.SerializerVersion);
  266. Dictionary<int, Guid> mappedNodeIds = new();
  267. Dictionary<int, Guid> mappedKeyFrameIds = new();
  268. ResourceStorageLocator? resourceLocator = null;
  269. if (builderInstance.DocumentResources != null)
  270. {
  271. resourceLocator = ExtractResources(builderInstance.DocumentResources);
  272. }
  273. var viewModel = new DocumentViewModel();
  274. viewModel.Operations.ResizeCanvas(new VecI(builderInstance.Width, builderInstance.Height), ResizeAnchor.Center);
  275. var acc = viewModel.Internals.ActionAccumulator;
  276. ColorSpace targetProcessingColorSpace = ColorSpace.CreateSrgbLinear();
  277. if (builderInstance.UsesSrgbColorBlending ||
  278. IsFileWithSrgbColorBlending(serializerData, builderInstance.PixiParserVersionUsed))
  279. {
  280. targetProcessingColorSpace = ColorSpace.CreateSrgb();
  281. viewModel.Internals.Tracker.Document.InitProcessingColorSpace(ColorSpace.CreateSrgb());
  282. viewModel.UsesSrgbBlending = true;
  283. }
  284. viewModel.Internals.ChangeController.SymmetryDraggedInlet(
  285. new SymmetryAxisDragInfo(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2));
  286. viewModel.Internals.ChangeController.SymmetryDraggedInlet(
  287. new SymmetryAxisDragInfo(SymmetryAxisDirection.Vertical, builderInstance.Width / 2));
  288. acc.AddActions(
  289. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Horizontal, (double)builderInstance.Height / 2),
  290. new EndSymmetryAxisPosition_Action(),
  291. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, (double)builderInstance.Width / 2),
  292. new EndSymmetryAxisPosition_Action());
  293. if (builderInstance.ReferenceLayer is { } refLayer)
  294. {
  295. acc.AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImageBgra8888Bytes.ToImmutableArray(),
  296. refLayer.ImageSize));
  297. }
  298. viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
  299. viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
  300. SerializationConfig config =
  301. new SerializationConfig(BuiltInEncoders.Encoders[builderInstance.ImageEncoderUsed],
  302. targetProcessingColorSpace);
  303. List<SerializationFactory> allFactories =
  304. ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList();
  305. foreach (var factory in allFactories)
  306. {
  307. factory.ResourceLocator = resourceLocator;
  308. }
  309. AddNodes(builderInstance.Graph);
  310. if (builderInstance.Graph.AllNodes.Count == 0 || !builderInstance.Graph.AllNodes.Any(x => x is OutputNode))
  311. {
  312. Guid outputNodeGuid = Guid.NewGuid();
  313. acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
  314. }
  315. AddAnimationData(builderInstance.AnimationData, mappedNodeIds, mappedKeyFrameIds);
  316. acc.AddFinishedActions(new ChangeBoundary_Action(), new DeleteRecordedChanges_Action());
  317. acc.AddActions(new InvokeAction_PassthroughAction(() =>
  318. {
  319. viewModel.MarkAsSaved();
  320. }));
  321. foreach (var factory in allFactories)
  322. {
  323. factory.ResourceLocator = null;
  324. }
  325. return viewModel;
  326. void AddNodes(NodeGraphBuilder graph)
  327. {
  328. foreach (var node in graph.AllNodes)
  329. {
  330. AddNode(node.Id, node);
  331. }
  332. foreach (var node in graph.AllNodes)
  333. {
  334. Guid nodeGuid = mappedNodeIds[node.Id];
  335. var serializedNode = graph.AllNodes.First(x => x.Id == node.Id);
  336. if (serializedNode.AdditionalData != null && serializedNode.AdditionalData.Count > 0)
  337. {
  338. acc.AddActions(new DeserializeNodeAdditionalData_Action(nodeGuid,
  339. SerializationUtil.DeserializeDict(serializedNode.AdditionalData, config, allFactories,
  340. serializerData)));
  341. }
  342. if (node.InputConnections != null)
  343. {
  344. foreach (var connections in node.InputConnections)
  345. {
  346. if (mappedNodeIds.TryGetValue(connections.Key, out Guid outputNodeId))
  347. {
  348. foreach (var connection in connections.Value)
  349. {
  350. acc.AddActions(new ConnectProperties_Action(nodeGuid, outputNodeId,
  351. connection.inputPropName, connection.outputPropName));
  352. }
  353. }
  354. }
  355. }
  356. }
  357. }
  358. void AddNode(int id, NodeGraphBuilder.NodeBuilder serializedNode)
  359. {
  360. Guid guid = Guid.NewGuid();
  361. mappedNodeIds.Add(id, guid);
  362. acc.AddActions(new CreateNodeFromName_Action(serializedNode.UniqueNodeName, guid));
  363. acc.AddFinishedActions(new NodePosition_Action([guid], serializedNode.Position.ToVecD()),
  364. new EndNodePosition_Action());
  365. if (serializedNode.InputValues != null)
  366. {
  367. foreach (var propertyValue in serializedNode.InputValues)
  368. {
  369. object value =
  370. SerializationUtil.Deserialize(propertyValue.Value, config, allFactories, serializerData);
  371. acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value));
  372. }
  373. }
  374. if (serializedNode.KeyFrames != null)
  375. {
  376. foreach (var keyFrame in serializedNode.KeyFrames)
  377. {
  378. Guid keyFrameGuid = Guid.NewGuid();
  379. /*Add should be here I think, but it crashes while deserializing multiple layers with no frames*/
  380. mappedKeyFrameIds[keyFrame.Id] = keyFrameGuid;
  381. acc.AddActions(
  382. new SetKeyFrameData_Action(
  383. guid,
  384. keyFrameGuid,
  385. SerializationUtil.Deserialize(keyFrame.Data, config, allFactories, serializerData),
  386. keyFrame.StartFrame,
  387. keyFrame.Duration, keyFrame.AffectedElement, keyFrame.IsVisible));
  388. }
  389. }
  390. if (!string.IsNullOrEmpty(serializedNode.Name))
  391. {
  392. acc.AddActions(new SetNodeName_Action(guid, serializedNode.Name));
  393. }
  394. }
  395. void AddAnimationData(AnimationDataBuilder? data, Dictionary<int, Guid> mappedIds,
  396. Dictionary<int, Guid> mappedKeyFrameIds)
  397. {
  398. if (data is null)
  399. return;
  400. acc.AddActions(new SetFrameRate_Action(data.FrameRate));
  401. acc.AddActions(new SetOnionSettings_Action(data.OnionFrames, data.OnionOpacity));
  402. foreach (var keyFrame in data.KeyFrameGroups)
  403. {
  404. if (keyFrame is GroupKeyFrameBuilder group)
  405. {
  406. foreach (var child in group.Children)
  407. {
  408. acc.AddActions(
  409. new CreateCel_Action(
  410. mappedIds[child.NodeId],
  411. mappedKeyFrameIds[child.KeyFrameId],
  412. -1, -1, default));
  413. acc.AddFinishedActions();
  414. }
  415. }
  416. }
  417. }
  418. bool IsFileWithSrgbColorBlending((string serializerName, string serializerVersion) serializerData,
  419. Version? pixiParserVersionUsed)
  420. {
  421. if (pixiParserVersionUsed != null && pixiParserVersionUsed.Major < 5)
  422. {
  423. return true;
  424. }
  425. if (serializerData.serializerVersion == null || serializerData.serializerName == null)
  426. {
  427. return false;
  428. }
  429. try
  430. {
  431. Version parsedVersion = new Version(serializerData.serializerVersion);
  432. return serializerData.serializerName == "PixiEditor"
  433. && parsedVersion is { Major: 2, Minor: 0, Build: 0, Revision: >= 28 and <= 31 };
  434. }
  435. catch (Exception)
  436. {
  437. return false;
  438. }
  439. }
  440. ResourceStorageLocator ExtractResources(ResourceStorage? resources)
  441. {
  442. if (resources is null)
  443. return null;
  444. string resourcesPath = Paths.TempResourcesPath;
  445. if (!Directory.Exists(resourcesPath))
  446. Directory.CreateDirectory(resourcesPath);
  447. Dictionary<int, string> mapping = new();
  448. foreach (var resource in resources.Resources)
  449. {
  450. string formattedGuid = resource.CacheId.ToString("N");
  451. string filePath = Path.Combine(resourcesPath, $"{formattedGuid}{Path.GetExtension(resource.FileName)}");
  452. File.WriteAllBytes(filePath, resource.Data);
  453. mapping.Add(resource.Handle, filePath);
  454. }
  455. return new ResourceStorageLocator(mapping, resourcesPath);
  456. }
  457. }
  458. public void MarkAsSaved()
  459. {
  460. Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Saved));
  461. }
  462. public void MarkAsAutosaved()
  463. {
  464. Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Autosaved));
  465. }
  466. public void MarkAsUnsaved()
  467. {
  468. Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Unsaved));
  469. }
  470. public void InternalMarkSaveState(DocumentMarkType type)
  471. {
  472. switch (type)
  473. {
  474. case DocumentMarkType.Saved:
  475. lastChangeOnSave = Internals.Tracker.LastChangeGuid;
  476. OnPropertyChanged(nameof(AllChangesSaved));
  477. break;
  478. case DocumentMarkType.Unsaved:
  479. lastChangeOnSave = Guid.NewGuid();
  480. OnPropertyChanged(nameof(AllChangesSaved));
  481. break;
  482. case DocumentMarkType.Autosaved:
  483. lastChangeOnAutosave = Internals.Tracker.LastChangeGuid;
  484. OnPropertyChanged(nameof(AllChangesAutosaved));
  485. break;
  486. case DocumentMarkType.UnAutosaved:
  487. lastChangeOnAutosave = Guid.NewGuid();
  488. OnPropertyChanged(nameof(AllChangesAutosaved));
  489. break;
  490. }
  491. }
  492. public OneOf<Error, Surface> TryRenderWholeImage(KeyFrameTime frameTime, VecI renderSize)
  493. {
  494. try
  495. {
  496. Surface finalSurface = null;
  497. DrawingBackendApi.Current.RenderingDispatcher.Invoke(() =>
  498. {
  499. finalSurface = new Surface(renderSize);
  500. finalSurface.DrawingSurface.Canvas.Save();
  501. VecD scaling = new VecD(renderSize.X / (double)SizeBindable.X, renderSize.Y / (double)SizeBindable.Y);
  502. finalSurface.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
  503. Renderer.RenderDocument(finalSurface.DrawingSurface, frameTime, renderSize);
  504. finalSurface.DrawingSurface.Canvas.Restore();
  505. });
  506. return finalSurface;
  507. }
  508. catch (ObjectDisposedException)
  509. {
  510. return new Error();
  511. }
  512. }
  513. /// <summary>
  514. /// Tries rendering the whole document
  515. /// </summary>
  516. /// <returns><see cref="Error"/> if the ChunkyImage was disposed, otherwise a <see cref="Surface"/> of the rendered document</returns>
  517. public OneOf<Error, Surface> TryRenderWholeImage(KeyFrameTime frameTime)
  518. {
  519. return TryRenderWholeImage(frameTime, SizeBindable);
  520. }
  521. /// <summary>
  522. /// Takes the selected area and converts it into a surface
  523. /// </summary>
  524. /// <returns><see cref="Error"/> on error, <see cref="None"/> for empty <see cref="Surface"/>, <see cref="Surface"/> otherwise.</returns>
  525. public OneOf<Error, None, (Surface, RectI)> TryExtractAreaFromSelected(
  526. RectI bounds)
  527. {
  528. var selectedLayers = ExtractSelectedLayers(true);
  529. if (selectedLayers.Count == 0)
  530. return new Error();
  531. if (bounds.IsZeroOrNegativeArea)
  532. return new None();
  533. RectI finalBounds = default;
  534. for (int i = 0; i < selectedLayers.Count; i++)
  535. {
  536. var memberVm = StructureHelper.Find(selectedLayers.ElementAt(i));
  537. IReadOnlyStructureNode? layer = Internals.Tracker.Document.FindMember(memberVm.Id);
  538. if (layer is null)
  539. return new Error();
  540. RectI? memberImageBounds;
  541. try
  542. {
  543. memberImageBounds = (RectI?)layer.GetTightBounds(AnimationDataViewModel.ActiveFrameTime);
  544. }
  545. catch (ObjectDisposedException)
  546. {
  547. return new Error();
  548. }
  549. if (memberImageBounds is null)
  550. continue;
  551. RectI combinedBounds = bounds.Intersect(memberImageBounds.Value);
  552. combinedBounds = combinedBounds.Intersect(new RectI(VecI.Zero, SizeBindable));
  553. if (combinedBounds.IsZeroOrNegativeArea)
  554. continue;
  555. if (i == 0 || finalBounds == default)
  556. {
  557. finalBounds = combinedBounds;
  558. }
  559. else
  560. {
  561. finalBounds = finalBounds.Union(combinedBounds);
  562. }
  563. }
  564. if (finalBounds.IsZeroOrNegativeArea)
  565. return new None();
  566. Surface output = new(finalBounds.Size);
  567. VectorPath clipPath = new VectorPath(SelectionPathBindable) { FillType = PathFillType.EvenOdd };
  568. //clipPath.Transform(Matrix3X3.CreateTranslation(-bounds.X, -bounds.Y));
  569. output.DrawingSurface.Canvas.Save();
  570. output.DrawingSurface.Canvas.Translate(-finalBounds.X, -finalBounds.Y);
  571. if (!clipPath.IsEmpty)
  572. {
  573. output.DrawingSurface.Canvas.ClipPath(clipPath);
  574. }
  575. using Paint paint = new Paint() { BlendMode = BlendMode.SrcOver };
  576. DrawingBackendApi.Current.RenderingDispatcher.Invoke(() =>
  577. {
  578. try
  579. {
  580. Renderer.RenderLayers(output.DrawingSurface, selectedLayers.ToHashSet(),
  581. AnimationDataViewModel.ActiveFrameBindable, ChunkResolution.Full, SizeBindable);
  582. }
  583. catch (ObjectDisposedException)
  584. {
  585. output?.Dispose();
  586. }
  587. });
  588. output.DrawingSurface.Canvas.Restore();
  589. return (output, finalBounds);
  590. }
  591. /// <summary>
  592. /// Picks the color at <paramref name="pos"/>
  593. /// </summary>
  594. /// <param name="includeReference">Should the color be picked from the reference layer</param>
  595. /// <param name="includeCanvas">Should the color be picked from the canvas</param>
  596. /// <param name="referenceTopmost">Is the reference layer topmost. (Only affects the result is includeReference and includeCanvas are set.)</param>
  597. public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas, int frame,
  598. bool referenceTopmost = false)
  599. {
  600. if (scope == DocumentScope.SingleLayer && includeReference && includeCanvas)
  601. includeReference = false;
  602. if (includeCanvas && includeReference)
  603. {
  604. Color canvasColor = PickColorFromCanvas((VecI)pos, scope, frame);
  605. Color? potentialReferenceColor = PickColorFromReferenceLayer(pos);
  606. if (potentialReferenceColor is not { } referenceColor)
  607. return canvasColor;
  608. if (!referenceTopmost)
  609. {
  610. return ColorHelpers.BlendColors(referenceColor, canvasColor);
  611. }
  612. byte referenceAlpha = canvasColor.A == 0
  613. ? referenceColor.A
  614. : (byte)(referenceColor.A * ReferenceLayerViewModel.TopMostOpacity);
  615. referenceColor = new Color(referenceColor.R, referenceColor.G, referenceColor.B, referenceAlpha);
  616. return ColorHelpers.BlendColors(canvasColor, referenceColor);
  617. }
  618. if (includeCanvas)
  619. return PickColorFromCanvas((VecI)pos, scope, frame);
  620. if (includeReference)
  621. return PickColorFromReferenceLayer(pos) ?? Colors.Transparent;
  622. return Colors.Transparent;
  623. }
  624. public Color? PickColorFromReferenceLayer(VecD pos)
  625. {
  626. Texture? bitmap = ReferenceLayerViewModel.ReferenceTexture;
  627. if (bitmap is null)
  628. return null;
  629. Matrix3X3 matrix = ReferenceLayerViewModel.ReferenceTransformMatrix;
  630. matrix = matrix.Invert();
  631. var transformed = matrix.MapPoint(pos);
  632. if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.Size.X || transformed.Y >= bitmap.Size.Y)
  633. return null;
  634. return bitmap.GetSRGBPixel(new VecI((int)transformed.X, (int)transformed.Y));
  635. }
  636. public Color PickColorFromCanvas(VecI pos, DocumentScope scope, KeyFrameTime frameTime)
  637. {
  638. // there is a tiny chance that the image might get disposed by another thread
  639. try
  640. {
  641. // it might've been a better idea to implement this function asynchronously
  642. // via a passthrough action to avoid all the try catches
  643. if (scope == DocumentScope.Canvas)
  644. {
  645. using Surface tmpSurface = new Surface(SizeBindable); // new Surface is on purpose, Surface.ForDisplay doesn't work here
  646. Renderer.RenderDocument(tmpSurface.DrawingSurface, frameTime, SizeBindable);
  647. return tmpSurface.GetSrgbPixel(pos);
  648. }
  649. if (SelectedStructureMember is not ILayerHandler layerVm)
  650. return Colors.Transparent;
  651. IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
  652. if (maybeMember is not IReadOnlyImageNode layer)
  653. {
  654. if (maybeMember is IRasterizable rasterizable)
  655. {
  656. using Texture texture = Texture.ForDisplay(SizeBindable);
  657. using Paint paint = new Paint();
  658. rasterizable.Rasterize(texture.DrawingSurface, paint);
  659. return texture.GetSRGBPixel(pos);
  660. }
  661. }
  662. else
  663. {
  664. return layer.GetLayerImageAtFrame(frameTime.Frame).GetMostUpToDatePixel(pos);
  665. }
  666. return Colors.Transparent;
  667. }
  668. catch (ObjectDisposedException)
  669. {
  670. return Colors.Transparent;
  671. }
  672. }
  673. #region Internal Methods
  674. // these are intended to only be called from DocumentUpdater
  675. public void InternalRaiseLayersChanged(LayersChangedEventArgs args) => LayersChanged?.Invoke(this, args);
  676. public void RaiseSizeChanged(DocumentSizeChangedEventArgs args) => SizeChanged?.Invoke(this, args);
  677. public void ISetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
  678. {
  679. this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
  680. OnPropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
  681. }
  682. public void SetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
  683. {
  684. this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
  685. OnPropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
  686. OnPropertyChanged(nameof(AnySymmetryAxisEnabledBindable));
  687. }
  688. public void SetVerticalSymmetryAxisEnabled(bool infoState)
  689. {
  690. verticalSymmetryAxisEnabled = infoState;
  691. OnPropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
  692. OnPropertyChanged(nameof(AnySymmetryAxisEnabledBindable));
  693. }
  694. public void SetVerticalSymmetryAxisX(double verticalSymmetryAxisX)
  695. {
  696. this.verticalSymmetryAxisX = verticalSymmetryAxisX;
  697. OnPropertyChanged(nameof(VerticalSymmetryAxisXBindable));
  698. }
  699. public void SetSelectedMember(IStructureMemberHandler member)
  700. {
  701. SelectedStructureMember = member;
  702. Internals.ChangeController.MembersSelectedInlet(GetSelectedMembers());
  703. OnPropertyChanged(nameof(SelectedStructureMember));
  704. }
  705. public void SetHorizontalSymmetryAxisY(double horizontalSymmetryAxisY)
  706. {
  707. this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
  708. OnPropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
  709. }
  710. public void SetProcessingColorSpace(ColorSpace infoColorSpace)
  711. {
  712. UsesSrgbBlending = infoColorSpace.IsSrgb;
  713. }
  714. public void SetSize(VecI size)
  715. {
  716. var oldSize = size;
  717. this.size = size;
  718. OnPropertyChanged(nameof(SizeBindable));
  719. OnPropertyChanged(nameof(Width));
  720. OnPropertyChanged(nameof(Height));
  721. // TODO: Make sure this is correct, it was in InternalRaiseSizeChanged previously, check DocumentUpdater.cs ProcessSize
  722. SizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(this, oldSize, size));
  723. }
  724. public void UpdateSelectionPath(VectorPath vectorPath)
  725. {
  726. (VectorPath? toDispose, this.selectionPath) = (this.selectionPath, vectorPath);
  727. toDispose.Dispose();
  728. OnPropertyChanged(nameof(SelectionPathBindable));
  729. }
  730. public void AddSoftSelectedMember(IStructureMemberHandler member)
  731. {
  732. softSelectedStructureMembers.Add(member);
  733. Internals.ChangeController.MembersSelectedInlet(GetSelectedMembers());
  734. OnPropertyChanged(nameof(SoftSelectedStructureMembers));
  735. }
  736. public void RemoveSoftSelectedMember(IStructureMemberHandler member)
  737. {
  738. softSelectedStructureMembers.Remove(member);
  739. Internals.ChangeController.MembersSelectedInlet(GetSelectedMembers());
  740. OnPropertyChanged(nameof(SoftSelectedStructureMembers));
  741. }
  742. public void ClearSoftSelectedMembers()
  743. {
  744. softSelectedStructureMembers.Clear();
  745. Internals.ChangeController.MembersSelectedInlet(GetSelectedMembers());
  746. OnPropertyChanged(nameof(SoftSelectedStructureMembers));
  747. }
  748. #endregion
  749. /// <summary>
  750. /// Returns a list of all selected members (Hard and Soft selected)
  751. /// </summary>
  752. public List<Guid> GetSelectedMembers()
  753. {
  754. List<Guid> layerGuids = new List<Guid>();
  755. if (SelectedStructureMember is not null)
  756. layerGuids.Add(SelectedStructureMember.Id);
  757. foreach (var member in softSelectedStructureMembers)
  758. {
  759. if (member.Id != SelectedStructureMember?.Id)
  760. {
  761. layerGuids.Add(member.Id);
  762. }
  763. }
  764. return layerGuids;
  765. }
  766. /// <summary>
  767. /// Gets all selected layers extracted from selected folders.
  768. /// </summary>
  769. /// <param name="includeFoldersWithMask">Should folders with mask be included</param>
  770. /// <returns>A list of GUIDs of selected layers</returns>
  771. public HashSet<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false)
  772. {
  773. var result = new HashSet<Guid>();
  774. List<Guid> selectedMembers = GetSelectedMembers();
  775. var allLayers = StructureHelper.GetAllMembers();
  776. foreach (var member in allLayers)
  777. {
  778. if (!selectedMembers.Contains(member.Id))
  779. continue;
  780. if (member is ILayerHandler)
  781. {
  782. result.Add(member.Id);
  783. }
  784. else if (member is IFolderHandler folder)
  785. {
  786. if (includeFoldersWithMask && folder.HasMaskBindable)
  787. result.Add(folder.Id);
  788. ExtractSelectedLayers(folder, result, includeFoldersWithMask);
  789. }
  790. }
  791. return result;
  792. }
  793. public void UpdateSavedState()
  794. {
  795. OnPropertyChanged(nameof(AllChangesSaved));
  796. }
  797. private void ExtractSelectedLayers(IFolderHandler folder, HashSet<Guid> list,
  798. bool includeFoldersWithMask)
  799. {
  800. foreach (var member in folder.Children)
  801. {
  802. if (member is ILayerHandler layer && !list.Contains(layer.Id))
  803. {
  804. list.Add(layer.Id);
  805. }
  806. else if (member is IFolderHandler childFolder)
  807. {
  808. if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.Id))
  809. list.Add(childFolder.Id);
  810. ExtractSelectedLayers(childFolder, list, includeFoldersWithMask);
  811. }
  812. }
  813. }
  814. public Image[] RenderFrames(Func<Surface, Surface> processFrameAction = null, CancellationToken token = default)
  815. {
  816. if (AnimationDataViewModel.KeyFrames.Count == 0)
  817. return [];
  818. if (token.IsCancellationRequested)
  819. return [];
  820. int firstFrame = AnimationDataViewModel.FirstFrame;
  821. int lastFrame = AnimationDataViewModel.LastFrame;
  822. int framesCount = lastFrame - firstFrame;
  823. Image[] images = new Image[framesCount];
  824. // TODO: Multi-threading
  825. for (int i = firstFrame; i < lastFrame; i++)
  826. {
  827. if (token.IsCancellationRequested)
  828. return [];
  829. double normalizedTime = (double)(i - firstFrame) / framesCount;
  830. KeyFrameTime frameTime = new KeyFrameTime(i, normalizedTime);
  831. var surface = TryRenderWholeImage(frameTime);
  832. if (surface.IsT0)
  833. {
  834. continue;
  835. }
  836. if (processFrameAction is not null)
  837. {
  838. surface = processFrameAction(surface.AsT1);
  839. }
  840. images[i - firstFrame] = surface.AsT1.DrawingSurface.Snapshot();
  841. surface.AsT1.Dispose();
  842. }
  843. return images;
  844. }
  845. /// <summary>
  846. /// Render frames progressively and disposes the surface after processing.
  847. /// </summary>
  848. /// <param name="processFrameAction">Action to perform on rendered frame</param>
  849. /// <param name="token"></param>
  850. public void RenderFramesProgressive(Action<Surface, int> processFrameAction, CancellationToken token)
  851. {
  852. if (AnimationDataViewModel.KeyFrames.Count == 0)
  853. return;
  854. int firstFrame = AnimationDataViewModel.FirstFrame;
  855. int framesCount = AnimationDataViewModel.FramesCount;
  856. int lastFrame = firstFrame + framesCount;
  857. int activeFrame = AnimationDataViewModel.ActiveFrameBindable;
  858. for (int i = firstFrame; i < lastFrame; i++)
  859. {
  860. if (token.IsCancellationRequested)
  861. return;
  862. KeyFrameTime frameTime = new KeyFrameTime(i, (double)(i - firstFrame) / framesCount);
  863. var surface = TryRenderWholeImage(frameTime);
  864. if (surface.IsT0)
  865. {
  866. continue;
  867. }
  868. processFrameAction(surface.AsT1, i - firstFrame);
  869. surface.AsT1.Dispose();
  870. }
  871. }
  872. public bool RenderFrames(List<Image> frames, Func<Surface, Surface> processFrameAction = null)
  873. {
  874. var keyFrames = AnimationDataViewModel.KeyFrames;
  875. int firstFrame = 0;
  876. int lastFrame = AnimationDataViewModel.FramesCount;
  877. if (keyFrames.Count > 0)
  878. {
  879. firstFrame = keyFrames.Min(x => x.StartFrameBindable);
  880. lastFrame = keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable);
  881. }
  882. for (int i = firstFrame; i < lastFrame; i++)
  883. {
  884. KeyFrameTime frameTime = new KeyFrameTime(i, (double)(i - firstFrame) / (lastFrame - firstFrame));
  885. var surface = TryRenderWholeImage(frameTime);
  886. if (surface.IsT0)
  887. {
  888. return false;
  889. }
  890. if (processFrameAction is not null)
  891. {
  892. surface = processFrameAction(surface.AsT1);
  893. }
  894. var snapshot = surface.AsT1.DrawingSurface.Snapshot();
  895. frames.Add(snapshot);
  896. }
  897. return true;
  898. }
  899. private static void ClearTempFolder(string tempRenderingPath)
  900. {
  901. string[] files = Directory.GetFiles(tempRenderingPath);
  902. for (var i = 0; i < files.Length; i++)
  903. {
  904. var file = files[i];
  905. File.Delete(file);
  906. }
  907. }
  908. public void Dispose()
  909. {
  910. Internals.Tracker.Dispose();
  911. Internals.Tracker.Document.Dispose();
  912. }
  913. }