DocumentViewModel.cs 32 KB

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