2
0

DocumentViewModel.cs 48 KB

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