DocumentViewModel.cs 28 KB

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