DocumentViewModel.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  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 Avalonia;
  8. using Avalonia.Media.Imaging;
  9. using ChunkyImageLib;
  10. using ChunkyImageLib.DataHolders;
  11. using ChunkyImageLib.Operations;
  12. using Microsoft.CodeAnalysis.CSharp.Syntax;
  13. using Microsoft.Extensions.DependencyInjection;
  14. using PixiEditor.AvaloniaUI.Helpers;
  15. using PixiEditor.AvaloniaUI.Helpers.Collections;
  16. using PixiEditor.AvaloniaUI.Helpers.Extensions;
  17. using PixiEditor.AvaloniaUI.Models.Controllers;
  18. using PixiEditor.AvaloniaUI.Models.DocumentModels;
  19. using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
  20. using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
  21. using PixiEditor.AvaloniaUI.Models.Handlers;
  22. using PixiEditor.AvaloniaUI.Models.Position;
  23. using PixiEditor.AvaloniaUI.Models.Serialization;
  24. using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
  25. using PixiEditor.AvaloniaUI.Models.Structures;
  26. using PixiEditor.AvaloniaUI.Models.Tools;
  27. using PixiEditor.AvaloniaUI.ViewModels.Document.TransformOverlays;
  28. using PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
  29. using PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
  30. using PixiEditor.ChangeableDocument.Actions;
  31. using PixiEditor.ChangeableDocument.Actions.Generated;
  32. using PixiEditor.ChangeableDocument.Actions.Undo;
  33. using PixiEditor.ChangeableDocument.Changeables.Animations;
  34. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  35. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  36. using PixiEditor.ChangeableDocument.Changeables.Interfaces;
  37. using PixiEditor.ChangeableDocument.ChangeInfos;
  38. using PixiEditor.ChangeableDocument.Changes.NodeGraph;
  39. using PixiEditor.ChangeableDocument.Enums;
  40. using PixiEditor.ChangeableDocument.Rendering;
  41. using PixiEditor.DrawingApi.Core;
  42. using PixiEditor.DrawingApi.Core.Numerics;
  43. using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
  44. using PixiEditor.DrawingApi.Core.Surfaces.Vector;
  45. using PixiEditor.Extensions.Common.Localization;
  46. using PixiEditor.Extensions.CommonApi.Palettes;
  47. using PixiEditor.Numerics;
  48. using PixiEditor.Parser;
  49. using PixiEditor.Parser.Skia;
  50. using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
  51. using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
  52. using Node = PixiEditor.Parser.Graph.Node;
  53. using Point = Avalonia.Point;
  54. namespace PixiEditor.AvaloniaUI.ViewModels.Document;
  55. #nullable enable
  56. internal partial class DocumentViewModel : PixiObservableObject, IDocument
  57. {
  58. public event EventHandler<LayersChangedEventArgs>? LayersChanged;
  59. public event EventHandler<DocumentSizeChangedEventArgs>? SizeChanged;
  60. private bool busy = false;
  61. public bool Busy
  62. {
  63. get => busy;
  64. set => SetProperty(ref busy, value);
  65. }
  66. private string coordinatesString = "";
  67. public string CoordinatesString
  68. {
  69. get => coordinatesString;
  70. set => SetProperty(ref coordinatesString, value);
  71. }
  72. private string? fullFilePath = null;
  73. public string? FullFilePath
  74. {
  75. get => fullFilePath;
  76. set
  77. {
  78. SetProperty(ref fullFilePath, value);
  79. OnPropertyChanged(nameof(FileName));
  80. }
  81. }
  82. public string FileName
  83. {
  84. get => fullFilePath is null ? new LocalizedString("UNNAMED") : Path.GetFileName(fullFilePath);
  85. }
  86. private Guid? lastChangeOnSave = null;
  87. public bool AllChangesSaved
  88. {
  89. get
  90. {
  91. return Internals.Tracker.LastChangeGuid == lastChangeOnSave;
  92. }
  93. }
  94. public DateTime OpenedUTC { get; } = DateTime.UtcNow;
  95. private bool horizontalSymmetryAxisEnabled;
  96. public bool HorizontalSymmetryAxisEnabledBindable
  97. {
  98. get => horizontalSymmetryAxisEnabled;
  99. set
  100. {
  101. if (!Internals.ChangeController.IsChangeActive)
  102. Internals.ActionAccumulator.AddFinishedActions(
  103. new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
  104. }
  105. }
  106. private bool verticalSymmetryAxisEnabled;
  107. public bool VerticalSymmetryAxisEnabledBindable
  108. {
  109. get => verticalSymmetryAxisEnabled;
  110. set
  111. {
  112. if (!Internals.ChangeController.IsChangeActive)
  113. Internals.ActionAccumulator.AddFinishedActions(
  114. new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
  115. }
  116. }
  117. public bool AnySymmetryAxisEnabledBindable =>
  118. HorizontalSymmetryAxisEnabledBindable || VerticalSymmetryAxisEnabledBindable;
  119. private VecI size = new VecI(64, 64);
  120. public int Width => size.X;
  121. public int Height => size.Y;
  122. public VecI SizeBindable => size;
  123. private double horizontalSymmetryAxisY;
  124. public double HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
  125. private double verticalSymmetryAxisX;
  126. public double VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
  127. private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
  128. public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
  129. public bool PointerDragChangeInProgress =>
  130. Internals.ChangeController.IsChangeActive && Internals.ChangeController.LeftMousePressed;
  131. public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
  132. public bool HasSavedRedo => Internals.Tracker.HasSavedRedo;
  133. public NodeGraphViewModel NodeGraph { get; }
  134. public DocumentStructureModule StructureHelper { get; }
  135. public DocumentToolsModule Tools { get; }
  136. public DocumentOperationsModule Operations { get; }
  137. public DocumentRenderer Renderer { get; }
  138. public DocumentEventsModule EventInlet { get; }
  139. public ActionDisplayList ActionDisplays { get; } =
  140. new(() => ViewModelMain.Current.NotifyToolActionDisplayChanged());
  141. public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
  142. //TODO: It was DrawingSurface before, check if it's correct
  143. public Dictionary<ChunkResolution, Surface> Surfaces { get; set; } = new()
  144. {
  145. [ChunkResolution.Full] = new Surface(new VecI(64, 64)),
  146. [ChunkResolution.Half] = new Surface(new VecI(32, 32)),
  147. [ChunkResolution.Quarter] = new Surface(new VecI(16, 16)),
  148. [ChunkResolution.Eighth] = new Surface(new VecI(8, 8))
  149. };
  150. private Surface previewSurface;
  151. public Surface PreviewSurface
  152. {
  153. get => previewSurface;
  154. set
  155. {
  156. VecI? oldSize = previewSurface?.Size;
  157. SetProperty(ref previewSurface, value);
  158. OnPropertyChanged(nameof(Surfaces));
  159. if (oldSize != null && value != null && oldSize != value.Size)
  160. {
  161. RaiseSizeChanged(new DocumentSizeChangedEventArgs(this, oldSize.Value, value.Size));
  162. }
  163. }
  164. }
  165. private VectorPath selectionPath = new VectorPath();
  166. public VectorPath SelectionPathBindable => selectionPath;
  167. public ObservableCollection<PaletteColor> Swatches { get; set; } = new();
  168. public ObservableRangeCollection<PaletteColor> Palette { get; set; } = new();
  169. public DocumentTransformViewModel TransformViewModel { get; }
  170. public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
  171. public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
  172. public AnimationDataViewModel AnimationDataViewModel { get; }
  173. public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
  174. private DocumentInternalParts Internals { get; }
  175. INodeGraphHandler IDocument.NodeGraphHandler => NodeGraph;
  176. IDocumentOperations IDocument.Operations => Operations;
  177. ITransformHandler IDocument.TransformHandler => TransformViewModel;
  178. ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
  179. public ILayerHandlerFactory LayerHandlerFactory { get; }
  180. public IFolderHandlerFactory FolderHandlerFactory { get; }
  181. IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
  182. IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
  183. private DocumentViewModel()
  184. {
  185. var serviceProvider = ViewModelMain.Current.Services;
  186. Internals = new DocumentInternalParts(this, serviceProvider);
  187. Tools = new DocumentToolsModule(this, Internals);
  188. StructureHelper = new DocumentStructureModule(this);
  189. EventInlet = new DocumentEventsModule(this, Internals);
  190. Operations = new DocumentOperationsModule(this, Internals);
  191. LayerHandlerFactory = new LayerHandlerFactory(this);
  192. FolderHandlerFactory = new FolderHandlerFactory(this);
  193. AnimationDataViewModel = new(this, Internals);
  194. NodeGraph = new NodeGraphViewModel(this, Internals);
  195. TransformViewModel = new(this);
  196. TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
  197. LineToolOverlayViewModel = new();
  198. LineToolOverlayViewModel.LineMoved += (_, args) =>
  199. Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
  200. VecI previewSize = StructureMemberViewModel.CalculatePreviewSize(SizeBindable);
  201. PreviewSurface = new Surface(new VecI(previewSize.X, previewSize.Y));
  202. ReferenceLayerViewModel = new(this, Internals);
  203. Renderer = new DocumentRenderer(Internals.Tracker.Document);
  204. }
  205. /// <summary>
  206. /// Creates a new document using the <paramref name="builder"/>
  207. /// </summary>
  208. /// <returns>The created document</returns>
  209. public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
  210. {
  211. var builderInstance = new DocumentViewModelBuilder();
  212. builder(builderInstance);
  213. Dictionary<int, Guid> mappedNodeIds = new();
  214. Dictionary<int, Guid> mappedKeyFrameIds = new();
  215. var viewModel = new DocumentViewModel();
  216. viewModel.Operations.ResizeCanvas(new VecI(builderInstance.Width, builderInstance.Height), ResizeAnchor.Center);
  217. var acc = viewModel.Internals.ActionAccumulator;
  218. viewModel.Internals.ChangeController.SymmetryDraggedInlet(
  219. new SymmetryAxisDragInfo(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2));
  220. viewModel.Internals.ChangeController.SymmetryDraggedInlet(
  221. new SymmetryAxisDragInfo(SymmetryAxisDirection.Vertical, builderInstance.Width / 2));
  222. acc.AddActions(
  223. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Horizontal, (double)builderInstance.Height / 2),
  224. new EndSymmetryAxisPosition_Action(),
  225. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, (double)builderInstance.Width / 2),
  226. new EndSymmetryAxisPosition_Action());
  227. if (builderInstance.ReferenceLayer is { } refLayer)
  228. {
  229. acc.AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImageBgra8888Bytes.ToImmutableArray(),
  230. refLayer.ImageSize));
  231. }
  232. viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
  233. viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
  234. SerializationConfig config =
  235. new SerializationConfig(BuiltInEncoders.Encoders[builderInstance.ImageEncoderUsed]);
  236. List<SerializationFactory> allFactories =
  237. ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList();
  238. AddNodes(builderInstance.Graph);
  239. if (builderInstance.Graph.AllNodes.Count == 0)
  240. {
  241. Guid outputNodeGuid = Guid.NewGuid();
  242. acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
  243. }
  244. AddAnimationData(builderInstance.AnimationData, mappedNodeIds, mappedKeyFrameIds);
  245. acc.AddFinishedActions(new DeleteRecordedChanges_Action());
  246. viewModel.MarkAsSaved();
  247. return viewModel;
  248. void AddNodes(NodeGraphBuilder graph)
  249. {
  250. foreach (var node in graph.AllNodes)
  251. {
  252. AddNode(node.Id, node);
  253. }
  254. foreach (var node in graph.AllNodes)
  255. {
  256. Guid nodeGuid = mappedNodeIds[node.Id];
  257. var serializedNode = graph.AllNodes.First(x => x.Id == node.Id);
  258. if (serializedNode.AdditionalData != null && serializedNode.AdditionalData.Count > 0)
  259. {
  260. acc.AddActions(new DeserializeNodeAdditionalData_Action(nodeGuid,
  261. SerializationUtil.DeserializeDict(serializedNode.AdditionalData, config, allFactories)));
  262. }
  263. if (node.InputConnections != null)
  264. {
  265. foreach (var connections in node.InputConnections)
  266. {
  267. if (mappedNodeIds.TryGetValue(connections.Key, out Guid outputNodeId))
  268. {
  269. foreach (var connection in connections.Value)
  270. {
  271. acc.AddActions(new ConnectProperties_Action(nodeGuid, outputNodeId,
  272. connection.inputPropName, connection.outputPropName));
  273. }
  274. }
  275. }
  276. }
  277. }
  278. }
  279. void AddNode(int id, NodeGraphBuilder.NodeBuilder serializedNode)
  280. {
  281. Guid guid = Guid.NewGuid();
  282. mappedNodeIds.Add(id, guid);
  283. acc.AddActions(new CreateNodeFromName_Action(serializedNode.UniqueNodeName, guid));
  284. acc.AddFinishedActions(new NodePosition_Action(guid, serializedNode.Position.ToVecD()),
  285. new EndNodePosition_Action());
  286. if (serializedNode.InputValues != null)
  287. {
  288. foreach (var propertyValue in serializedNode.InputValues)
  289. {
  290. object value = SerializationUtil.Deserialize(propertyValue.Value, config, allFactories);
  291. acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value));
  292. }
  293. }
  294. if (serializedNode.KeyFrames != null)
  295. {
  296. foreach (var keyFrame in serializedNode.KeyFrames)
  297. {
  298. Guid keyFrameGuid = Guid.NewGuid();
  299. /*Add should be here I think, but it crashes while deserializing multiple layers with no frames*/
  300. mappedKeyFrameIds[keyFrame.Id] = keyFrameGuid;
  301. acc.AddActions(
  302. new SetKeyFrameData_Action(
  303. guid,
  304. keyFrameGuid,
  305. SerializationUtil.Deserialize(keyFrame.Data, config, allFactories),
  306. keyFrame.StartFrame,
  307. keyFrame.Duration, keyFrame.AffectedElement, keyFrame.IsVisible));
  308. }
  309. }
  310. if (!string.IsNullOrEmpty(serializedNode.Name))
  311. {
  312. acc.AddActions(new SetNodeName_Action(guid, serializedNode.Name));
  313. }
  314. }
  315. /*void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
  316. {
  317. acc.AddActions(
  318. new CreateStructureMember_Action(parentGuid, member.Id,
  319. member is DocumentViewModelBuilder.LayerBuilder
  320. ? StructureMemberType.Layer
  321. : StructureMemberType.Folder),
  322. new StructureMemberName_Action(member.Id, member.Name)
  323. );
  324. if (!member.IsVisible)
  325. acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.Id));
  326. acc.AddActions(new StructureMemberBlendMode_Action(member.BlendMode, member.Id));
  327. acc.AddActions(new StructureMemberClipToMemberBelow_Action(member.ClipToMemberBelow, member.Id));
  328. if (member is DocumentViewModelBuilder.LayerBuilder layerBuilder)
  329. {
  330. acc.AddActions(new LayerLockTransparency_Action(layerBuilder.Id, layerBuilder.LockAlpha));
  331. }
  332. if (member is DocumentViewModelBuilder.LayerBuilder layer && layer.Surface is not null)
  333. {
  334. PasteImage(member.Id, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY,
  335. false, 0);
  336. }
  337. acc.AddActions(
  338. new StructureMemberOpacity_Action(member.Id, member.Opacity),
  339. new EndStructureMemberOpacity_Action());
  340. if (member.HasMask)
  341. {
  342. var maskSurface = member.Mask.Surface.Surface;
  343. acc.AddActions(new CreateStructureMemberMask_Action(member.Id));
  344. if (!member.Mask.IsVisible)
  345. acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.Id));
  346. PasteImage(member.Id, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true, 0);
  347. }
  348. acc.AddFinishedActions();
  349. if (member is DocumentViewModelBuilder.FolderBuilder { Children: not null } folder)
  350. {
  351. AddMembers(member.Id, folder.Children);
  352. }
  353. }*/
  354. /*void PasteImage(Guid guid, DocumentViewModelBuilder.SurfaceBuilder surface, int width, int height, int offsetX,
  355. int offsetY, bool onMask, int frame, Guid? keyFrameGuid = default)
  356. {
  357. acc.AddActions(
  358. new PasteImage_Action(surface.Surface, new(new RectD(new VecD(offsetX, offsetY), new(width, height))),
  359. guid, true, onMask, frame, keyFrameGuid ?? default),
  360. new EndPasteImage_Action());
  361. }*/
  362. /*void AddMembers(Guid parentGuid, IEnumerable<DocumentViewModelBuilder.StructureMemberBuilder> builders)
  363. {
  364. foreach (var child in builders.Reverse())
  365. {
  366. if (child.Id == default)
  367. {
  368. child.Id = Guid.NewGuid();
  369. }
  370. AddMember(parentGuid, child);
  371. }
  372. }*/
  373. void AddAnimationData(AnimationDataBuilder? data, Dictionary<int, Guid> mappedIds,
  374. Dictionary<int, Guid> mappedKeyFrameIds)
  375. {
  376. if(data is null)
  377. return;
  378. acc.AddActions(new SetFrameRate_Action(data.FrameRate));
  379. foreach (var keyFrame in data.KeyFrameGroups)
  380. {
  381. if (keyFrame is GroupKeyFrameBuilder group)
  382. {
  383. foreach (var child in group.Children)
  384. {
  385. acc.AddActions(
  386. new CreateRasterKeyFrame_Action(
  387. mappedIds[child.NodeId],
  388. mappedKeyFrameIds[child.KeyFrameId],
  389. -1, -1, default));
  390. acc.AddFinishedActions();
  391. }
  392. }
  393. }
  394. }
  395. }
  396. public void MarkAsSaved()
  397. {
  398. lastChangeOnSave = Internals.Tracker.LastChangeGuid;
  399. OnPropertyChanged(nameof(AllChangesSaved));
  400. }
  401. public void MarkAsUnsaved()
  402. {
  403. lastChangeOnSave = Guid.NewGuid();
  404. OnPropertyChanged(nameof(AllChangesSaved));
  405. }
  406. /// <summary>
  407. /// Tries rendering the whole document
  408. /// </summary>
  409. /// <returns><see cref="Error"/> if the ChunkyImage was disposed, otherwise a <see cref="Surface"/> of the rendered document</returns>
  410. public OneOf<Error, Surface> TryRenderWholeImage(KeyFrameTime frameTime)
  411. {
  412. try
  413. {
  414. Surface finalSurface = new Surface(SizeBindable);
  415. VecI sizeInChunks = (VecI)((VecD)SizeBindable / ChunkyImage.FullChunkSize).Ceiling();
  416. for (int i = 0; i < sizeInChunks.X; i++)
  417. {
  418. for (int j = 0; j < sizeInChunks.Y; j++)
  419. {
  420. var maybeChunk = Renderer.RenderChunk(new(i, j), ChunkResolution.Full, frameTime);
  421. if (maybeChunk.IsT1)
  422. continue;
  423. using Chunk chunk = maybeChunk.AsT0;
  424. finalSurface.DrawingSurface.Canvas.DrawSurface(
  425. chunk.Surface.DrawingSurface,
  426. i * ChunkyImage.FullChunkSize, j * ChunkyImage.FullChunkSize);
  427. }
  428. }
  429. return finalSurface;
  430. }
  431. catch (ObjectDisposedException)
  432. {
  433. return new Error();
  434. }
  435. }
  436. /// <summary>
  437. /// Takes the selected area and converts it into a surface
  438. /// </summary>
  439. /// <returns><see cref="Error"/> on error, <see cref="None"/> for empty <see cref="Surface"/>, <see cref="Surface"/> otherwise.</returns>
  440. public OneOf<Error, None, (Surface, RectI)> MaybeExtractSelectedArea(
  441. IStructureMemberHandler? layerToExtractFrom = null)
  442. {
  443. layerToExtractFrom ??= SelectedStructureMember;
  444. if (layerToExtractFrom is not LayerViewModel layerVm)
  445. return new Error();
  446. if (SelectionPathBindable.IsEmpty)
  447. return new None();
  448. //TODO: Make sure it's not needed for other layer types
  449. IReadOnlyImageNode? layer = (IReadOnlyImageNode?)Internals.Tracker.Document.FindMember(layerVm.Id);
  450. if (layer is null)
  451. return new Error();
  452. RectI bounds = (RectI)SelectionPathBindable.TightBounds;
  453. RectI? memberImageBounds;
  454. try
  455. {
  456. // TODO: Make sure it must be GetLayerImageAtFrame rather than Rasterize()
  457. memberImageBounds = layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
  458. .FindChunkAlignedMostUpToDateBounds();
  459. }
  460. catch (ObjectDisposedException)
  461. {
  462. return new Error();
  463. }
  464. if (memberImageBounds is null)
  465. return new None();
  466. bounds = bounds.Intersect(memberImageBounds.Value);
  467. bounds = bounds.Intersect(new RectI(VecI.Zero, SizeBindable));
  468. if (bounds.IsZeroOrNegativeArea)
  469. return new None();
  470. Surface output = new(bounds.Size);
  471. VectorPath clipPath = new VectorPath(SelectionPathBindable) { FillType = PathFillType.EvenOdd };
  472. clipPath.Transform(Matrix3X3.CreateTranslation(-bounds.X, -bounds.Y));
  473. output.DrawingSurface.Canvas.Save();
  474. output.DrawingSurface.Canvas.ClipPath(clipPath);
  475. try
  476. {
  477. layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
  478. .DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
  479. }
  480. catch (ObjectDisposedException)
  481. {
  482. output.Dispose();
  483. return new Error();
  484. }
  485. output.DrawingSurface.Canvas.Restore();
  486. return (output, bounds);
  487. }
  488. /// <summary>
  489. /// Picks the color at <paramref name="pos"/>
  490. /// </summary>
  491. /// <param name="includeReference">Should the color be picked from the reference layer</param>
  492. /// <param name="includeCanvas">Should the color be picked from the canvas</param>
  493. /// <param name="referenceTopmost">Is the reference layer topmost. (Only affects the result is includeReference and includeCanvas are set.)</param>
  494. public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas, int frame,
  495. bool referenceTopmost = false)
  496. {
  497. if (scope == DocumentScope.SingleLayer && includeReference && includeCanvas)
  498. includeReference = false;
  499. if (includeCanvas && includeReference)
  500. {
  501. Color canvasColor = PickColorFromCanvas((VecI)pos, scope, frame);
  502. Color? potentialReferenceColor = PickColorFromReferenceLayer(pos);
  503. if (potentialReferenceColor is not { } referenceColor)
  504. return canvasColor;
  505. if (!referenceTopmost)
  506. {
  507. return ColorHelpers.BlendColors(referenceColor, canvasColor);
  508. }
  509. byte referenceAlpha = canvasColor.A == 0
  510. ? referenceColor.A
  511. : (byte)(referenceColor.A * ReferenceLayerViewModel.TopMostOpacity);
  512. referenceColor = new Color(referenceColor.R, referenceColor.G, referenceColor.B, referenceAlpha);
  513. return ColorHelpers.BlendColors(canvasColor, referenceColor);
  514. }
  515. if (includeCanvas)
  516. return PickColorFromCanvas((VecI)pos, scope, frame);
  517. if (includeReference)
  518. return PickColorFromReferenceLayer(pos) ?? Colors.Transparent;
  519. return Colors.Transparent;
  520. }
  521. public Color? PickColorFromReferenceLayer(VecD pos)
  522. {
  523. Surface? bitmap = ReferenceLayerViewModel.ReferenceBitmap;
  524. if (bitmap is null)
  525. return null;
  526. Matrix matrix = ReferenceLayerViewModel.ReferenceTransformMatrix;
  527. matrix = matrix.Invert();
  528. var transformed = matrix.Transform(new Point(pos.X, pos.Y));
  529. if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.Size.X || transformed.Y >= bitmap.Size.Y)
  530. return null;
  531. return bitmap.GetSRGBPixel(new VecI((int)transformed.X, (int)transformed.Y));
  532. }
  533. public Color PickColorFromCanvas(VecI pos, DocumentScope scope, KeyFrameTime frameTime)
  534. {
  535. // there is a tiny chance that the image might get disposed by another thread
  536. try
  537. {
  538. // it might've been a better idea to implement this function asynchronously
  539. // via a passthrough action to avoid all the try catches
  540. if (scope == DocumentScope.AllLayers)
  541. {
  542. VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
  543. return Renderer.RenderChunk(chunkPos, ChunkResolution.Full,
  544. frameTime)
  545. .Match(
  546. chunk =>
  547. {
  548. VecI posOnChunk = pos - chunkPos * ChunkyImage.FullChunkSize;
  549. var color = chunk.Surface.GetSRGBPixel(posOnChunk);
  550. chunk.Dispose();
  551. return color;
  552. },
  553. _ => Colors.Transparent);
  554. }
  555. if (SelectedStructureMember is not LayerViewModel layerVm)
  556. return Colors.Transparent;
  557. IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
  558. if (maybeMember is not IReadOnlyImageNode layer)
  559. return Colors.Transparent;
  560. return layer.GetLayerImageAtFrame(frameTime.Frame).GetMostUpToDatePixel(pos);
  561. }
  562. catch (ObjectDisposedException)
  563. {
  564. return Colors.Transparent;
  565. }
  566. }
  567. #region Internal Methods
  568. // these are intended to only be called from DocumentUpdater
  569. public void RaiseLayersChanged(LayersChangedEventArgs args) => LayersChanged?.Invoke(this, args);
  570. public void RaiseSizeChanged(DocumentSizeChangedEventArgs args) => SizeChanged?.Invoke(this, args);
  571. public void ISetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
  572. {
  573. this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
  574. OnPropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
  575. }
  576. public void SetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
  577. {
  578. this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
  579. OnPropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
  580. OnPropertyChanged(nameof(AnySymmetryAxisEnabledBindable));
  581. }
  582. public void SetVerticalSymmetryAxisEnabled(bool infoState)
  583. {
  584. verticalSymmetryAxisEnabled = infoState;
  585. OnPropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
  586. OnPropertyChanged(nameof(AnySymmetryAxisEnabledBindable));
  587. }
  588. public void SetVerticalSymmetryAxisX(double verticalSymmetryAxisX)
  589. {
  590. this.verticalSymmetryAxisX = verticalSymmetryAxisX;
  591. OnPropertyChanged(nameof(VerticalSymmetryAxisXBindable));
  592. }
  593. public void SetSelectedMember(IStructureMemberHandler member) =>
  594. SetSelectedMember((StructureMemberViewModel)member);
  595. public void SetHorizontalSymmetryAxisY(double horizontalSymmetryAxisY)
  596. {
  597. this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
  598. OnPropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
  599. }
  600. public void SetSize(VecI size)
  601. {
  602. var oldSize = size;
  603. this.size = size;
  604. OnPropertyChanged(nameof(SizeBindable));
  605. OnPropertyChanged(nameof(Width));
  606. OnPropertyChanged(nameof(Height));
  607. // TODO: Make sure this is correct, it was in InternalRaiseSizeChanged previously, check DocumentUpdater.cs ProcessSize
  608. SizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(this, oldSize, size));
  609. }
  610. public void UpdateSelectionPath(VectorPath vectorPath)
  611. {
  612. (VectorPath? toDispose, this.selectionPath) = (this.selectionPath, vectorPath);
  613. toDispose.Dispose();
  614. OnPropertyChanged(nameof(SelectionPathBindable));
  615. }
  616. public void SetSelectedMember(StructureMemberViewModel? member)
  617. {
  618. SelectedStructureMember = member;
  619. OnPropertyChanged(nameof(SelectedStructureMember));
  620. }
  621. public void RemoveSoftSelectedMember(IStructureMemberHandler member)
  622. {
  623. SelectedStructureMember = member;
  624. }
  625. public void ClearSoftSelectedMembers() => softSelectedStructureMembers.Clear();
  626. public void AddSoftSelectedMember(IStructureMemberHandler member) =>
  627. softSelectedStructureMembers.Add((StructureMemberViewModel)member);
  628. public void RemoveSoftSelectedMember(StructureMemberViewModel member) =>
  629. softSelectedStructureMembers.Remove(member);
  630. #endregion
  631. /// <summary>
  632. /// Returns a list of all selected members (Hard and Soft selected)
  633. /// </summary>
  634. public List<Guid> GetSelectedMembers()
  635. {
  636. List<Guid> layerGuids = new List<Guid>();
  637. if (SelectedStructureMember is not null)
  638. layerGuids.Add(SelectedStructureMember.Id);
  639. layerGuids.AddRange(softSelectedStructureMembers.Select(x => x.Id));
  640. return layerGuids;
  641. }
  642. /// <summary>
  643. /// Gets all selected layers extracted from selected folders.
  644. /// </summary>
  645. /// <param name="includeFoldersWithMask">Should folders with mask be included</param>
  646. /// <returns>A list of GUIDs of selected layers</returns>
  647. public List<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false)
  648. {
  649. var result = new List<Guid>();
  650. List<Guid> selectedMembers = GetSelectedMembers();
  651. foreach (var member in selectedMembers)
  652. {
  653. var foundMember = StructureHelper.Find(member);
  654. if (foundMember != null)
  655. {
  656. if (foundMember is LayerViewModel layer && selectedMembers.Contains(foundMember.Id) &&
  657. !result.Contains(layer.Id))
  658. {
  659. result.Add(layer.Id);
  660. }
  661. else if (foundMember is FolderViewModel folder && selectedMembers.Contains(foundMember.Id))
  662. {
  663. if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.Id))
  664. result.Add(folder.Id);
  665. ExtractSelectedLayers(folder, result, includeFoldersWithMask);
  666. }
  667. }
  668. }
  669. return result;
  670. }
  671. public void UpdateSavedState()
  672. {
  673. OnPropertyChanged(nameof(AllChangesSaved));
  674. }
  675. private void ExtractSelectedLayers(FolderViewModel folder, List<Guid> list,
  676. bool includeFoldersWithMask)
  677. {
  678. foreach (var member in folder.Children)
  679. {
  680. if (member is LayerViewModel layer && !list.Contains(layer.Id))
  681. {
  682. list.Add(layer.Id);
  683. }
  684. else if (member is FolderViewModel childFolder)
  685. {
  686. if (includeFoldersWithMask && childFolder.HasMaskBindable && !list.Contains(childFolder.Id))
  687. list.Add(childFolder.Id);
  688. ExtractSelectedLayers(childFolder, list, includeFoldersWithMask);
  689. }
  690. }
  691. }
  692. public Image[] RenderFrames(Func<Surface, Surface> processFrameAction = null)
  693. {
  694. if (AnimationDataViewModel.KeyFrames.Count == 0)
  695. return [];
  696. int firstFrame = AnimationDataViewModel.FirstFrame;
  697. int framesCount = AnimationDataViewModel.FramesCount;
  698. int lastFrame = firstFrame + framesCount;
  699. Image[] images = new Image[framesCount];
  700. for (int i = firstFrame; i < lastFrame; i++)
  701. {
  702. double normalizedTime = (double)(i - firstFrame) / framesCount;
  703. KeyFrameTime frameTime = new KeyFrameTime(i, normalizedTime);
  704. var surface = TryRenderWholeImage(frameTime);
  705. if (surface.IsT0)
  706. {
  707. continue;
  708. }
  709. if (processFrameAction is not null)
  710. {
  711. surface = processFrameAction(surface.AsT1);
  712. }
  713. images[i - firstFrame] = surface.AsT1.DrawingSurface.Snapshot();
  714. surface.AsT1.Dispose();
  715. }
  716. return images;
  717. }
  718. /// <summary>
  719. /// Render frames progressively and disposes the surface after processing.
  720. /// </summary>
  721. /// <param name="processFrameAction">Action to perform on rendered frame</param>
  722. public void RenderFramesProgressive(Action<Surface, int> processFrameAction)
  723. {
  724. if (AnimationDataViewModel.KeyFrames.Count == 0)
  725. return;
  726. int firstFrame = AnimationDataViewModel.FirstFrame;
  727. int framesCount = AnimationDataViewModel.FramesCount;
  728. int lastFrame = firstFrame + framesCount;
  729. int activeFrame = AnimationDataViewModel.ActiveFrameBindable;
  730. for (int i = firstFrame; i < lastFrame; i++)
  731. {
  732. var surface = TryRenderWholeImage(i);
  733. if (surface.IsT0)
  734. {
  735. continue;
  736. }
  737. processFrameAction(surface.AsT1, i - firstFrame);
  738. surface.AsT1.Dispose();
  739. }
  740. }
  741. public bool RenderFrames(string tempRenderingPath, Func<Surface, Surface> processFrameAction = null)
  742. {
  743. if (AnimationDataViewModel.KeyFrames.Count == 0)
  744. return false;
  745. if (!Directory.Exists(tempRenderingPath))
  746. {
  747. Directory.CreateDirectory(tempRenderingPath);
  748. }
  749. else
  750. {
  751. ClearTempFolder(tempRenderingPath);
  752. }
  753. var keyFrames = AnimationDataViewModel.KeyFrames;
  754. var firstFrame = keyFrames.Min(x => x.StartFrameBindable);
  755. var lastFrame = keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable);
  756. for (int i = firstFrame; i < lastFrame; i++)
  757. {
  758. KeyFrameTime frameTime = new KeyFrameTime(i, (double)(i - firstFrame) / (lastFrame - firstFrame));
  759. var surface = TryRenderWholeImage(frameTime);
  760. if (surface.IsT0)
  761. {
  762. return false;
  763. }
  764. if (processFrameAction is not null)
  765. {
  766. surface = processFrameAction(surface.AsT1);
  767. }
  768. using var stream = new FileStream(Path.Combine(tempRenderingPath, $"{i}.png"), FileMode.Create);
  769. surface.AsT1.DrawingSurface.Snapshot().Encode().SaveTo(stream);
  770. stream.Position = 0;
  771. }
  772. return true;
  773. }
  774. private static void ClearTempFolder(string tempRenderingPath)
  775. {
  776. string[] files = Directory.GetFiles(tempRenderingPath);
  777. for (var i = 0; i < files.Length; i++)
  778. {
  779. var file = files[i];
  780. File.Delete(file);
  781. }
  782. }
  783. public void Dispose()
  784. {
  785. foreach (var (_, surface) in Surfaces)
  786. {
  787. surface.Dispose();
  788. }
  789. PreviewSurface.Dispose();
  790. Internals.Tracker.Dispose();
  791. Internals.Tracker.Document.Dispose();
  792. }
  793. }