DocumentViewModel.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. using System.IO;
  2. using System.Windows;
  3. using System.Windows.Media;
  4. using System.Windows.Media.Imaging;
  5. using ChunkyImageLib;
  6. using ChunkyImageLib.DataHolders;
  7. using ChunkyImageLib.Operations;
  8. using Models.DocumentModels.Public;
  9. using PixiEditor.ChangeableDocument.Actions.Undo;
  10. using PixiEditor.ChangeableDocument.Changeables.Interfaces;
  11. using PixiEditor.ChangeableDocument.Enums;
  12. using PixiEditor.ChangeableDocument.Rendering;
  13. using PixiEditor.DrawingApi.Core.Numerics;
  14. using PixiEditor.DrawingApi.Core.Surface;
  15. using PixiEditor.DrawingApi.Core.Surface.ImageData;
  16. using PixiEditor.DrawingApi.Core.Surface.Vector;
  17. using PixiEditor.Helpers;
  18. using PixiEditor.Models.Controllers;
  19. using PixiEditor.Models.DataHolders;
  20. using PixiEditor.Models.DocumentModels;
  21. using PixiEditor.Models.DocumentModels.Public;
  22. using PixiEditor.Models.Enums;
  23. using PixiEditor.Views.UserControls.SymmetryOverlay;
  24. using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
  25. using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
  26. namespace PixiEditor.ViewModels.SubViewModels.Document;
  27. #nullable enable
  28. internal partial class DocumentViewModel : NotifyableObject
  29. {
  30. public event EventHandler<LayersChangedEventArgs>? LayersChanged;
  31. public event EventHandler<DocumentSizeChangedEventArgs>? SizeChanged;
  32. private bool busy = false;
  33. public bool Busy
  34. {
  35. get => busy;
  36. set => SetProperty(ref busy, value);
  37. }
  38. private string coordinatesString = "";
  39. public string CoordinatesString
  40. {
  41. get => coordinatesString;
  42. set => SetProperty(ref coordinatesString, value);
  43. }
  44. private string? fullFilePath = null;
  45. public string? FullFilePath
  46. {
  47. get => fullFilePath;
  48. set
  49. {
  50. SetProperty(ref fullFilePath, value);
  51. RaisePropertyChanged(nameof(FileName));
  52. }
  53. }
  54. public string FileName
  55. {
  56. get => fullFilePath is null ? "Unnamed" : Path.GetFileName(fullFilePath);
  57. }
  58. private Guid? lastChangeOnSave = null;
  59. public bool AllChangesSaved
  60. {
  61. get
  62. {
  63. return Internals.Tracker.LastChangeGuid == lastChangeOnSave;
  64. }
  65. }
  66. public DateTime OpenedUTC { get; } = DateTime.UtcNow;
  67. private bool horizontalSymmetryAxisEnabled;
  68. public bool HorizontalSymmetryAxisEnabledBindable
  69. {
  70. get => horizontalSymmetryAxisEnabled;
  71. set
  72. {
  73. if (!Internals.ChangeController.IsChangeActive)
  74. Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
  75. }
  76. }
  77. private bool verticalSymmetryAxisEnabled;
  78. public bool VerticalSymmetryAxisEnabledBindable
  79. {
  80. get => verticalSymmetryAxisEnabled;
  81. set
  82. {
  83. if (!Internals.ChangeController.IsChangeActive)
  84. Internals.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
  85. }
  86. }
  87. private VecI size = new VecI(64, 64);
  88. public int Width => size.X;
  89. public int Height => size.Y;
  90. public VecI SizeBindable => size;
  91. private int horizontalSymmetryAxisY;
  92. public int HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
  93. private int verticalSymmetryAxisX;
  94. public int VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
  95. private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
  96. public IReadOnlyCollection<StructureMemberViewModel> SoftSelectedStructureMembers => softSelectedStructureMembers;
  97. public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
  98. public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
  99. public bool HasSavedRedo => Internals.Tracker.HasSavedRedo;
  100. public FolderViewModel StructureRoot { get; }
  101. public DocumentStructureModule StructureHelper { get; }
  102. public DocumentToolsModule Tools { get; }
  103. public DocumentOperationsModule Operations { get; }
  104. public DocumentEventsModule EventInlet { get; }
  105. public StructureMemberViewModel? SelectedStructureMember { get; private set; } = null;
  106. public Dictionary<ChunkResolution, DrawingSurface> Surfaces { get; set; } = new();
  107. public Dictionary<ChunkResolution, WriteableBitmap> Bitmaps { get; set; } = new()
  108. {
  109. [ChunkResolution.Full] = new WriteableBitmap(64, 64, 96, 96, PixelFormats.Pbgra32, null),
  110. [ChunkResolution.Half] = new WriteableBitmap(32, 32, 96, 96, PixelFormats.Pbgra32, null),
  111. [ChunkResolution.Quarter] = new WriteableBitmap(16, 16, 96, 96, PixelFormats.Pbgra32, null),
  112. [ChunkResolution.Eighth] = new WriteableBitmap(8, 8, 96, 96, PixelFormats.Pbgra32, null),
  113. };
  114. public WriteableBitmap PreviewBitmap { get; set; }
  115. public DrawingSurface PreviewSurface { get; set; }
  116. private VectorPath selectionPath = new VectorPath();
  117. public VectorPath SelectionPathBindable => selectionPath;
  118. public WpfObservableRangeCollection<Color> Swatches { get; set; } = new WpfObservableRangeCollection<Color>();
  119. public WpfObservableRangeCollection<Color> Palette { get; set; } = new WpfObservableRangeCollection<Color>();
  120. public DocumentTransformViewModel TransformViewModel { get; }
  121. public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
  122. public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
  123. private DocumentInternalParts Internals { get; }
  124. private DocumentViewModel()
  125. {
  126. Internals = new DocumentInternalParts(this);
  127. Tools = new DocumentToolsModule(this, Internals);
  128. StructureHelper = new DocumentStructureModule(this);
  129. EventInlet = new DocumentEventsModule(this, Internals);
  130. Operations = new DocumentOperationsModule(this, Internals);
  131. StructureRoot = new FolderViewModel(this, Internals, Internals.Tracker.Document.StructureRoot.GuidValue);
  132. TransformViewModel = new();
  133. TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
  134. LineToolOverlayViewModel = new();
  135. LineToolOverlayViewModel.LineMoved += (_, args) => Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
  136. foreach (KeyValuePair<ChunkResolution, WriteableBitmap> bitmap in Bitmaps)
  137. {
  138. DrawingSurface? surface = DrawingSurface.Create(
  139. new ImageInfo(bitmap.Value.PixelWidth, bitmap.Value.PixelHeight, ColorType.Bgra8888, AlphaType.Premul, ColorSpace.CreateSrgb()),
  140. bitmap.Value.BackBuffer, bitmap.Value.BackBufferStride);
  141. Surfaces[bitmap.Key] = surface;
  142. }
  143. VecI previewSize = StructureMemberViewModel.CalculatePreviewSize(SizeBindable);
  144. PreviewBitmap = new WriteableBitmap(previewSize.X, previewSize.Y, 96, 96, PixelFormats.Pbgra32, null);
  145. PreviewSurface = DrawingSurface.Create(new ImageInfo(previewSize.X, previewSize.Y, ColorType.Bgra8888), PreviewBitmap.BackBuffer, PreviewBitmap.BackBufferStride);
  146. ReferenceLayerViewModel = new(this, Internals);
  147. }
  148. public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
  149. {
  150. var builderInstance = new DocumentViewModelBuilder();
  151. builder(builderInstance);
  152. var viewModel = new DocumentViewModel();
  153. viewModel.Operations.ResizeCanvas(new VecI(builderInstance.Width, builderInstance.Height), ResizeAnchor.Center);
  154. var acc = viewModel.Internals.ActionAccumulator;
  155. viewModel.Internals.ChangeController.SymmetryDraggedInlet(new SymmetryAxisDragInfo(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2));
  156. viewModel.Internals.ChangeController.SymmetryDraggedInlet(new SymmetryAxisDragInfo(SymmetryAxisDirection.Vertical, builderInstance.Width / 2));
  157. acc.AddActions(
  158. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2),
  159. new EndSymmetryAxisPosition_Action(),
  160. new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, builderInstance.Width / 2),
  161. new EndSymmetryAxisPosition_Action());
  162. viewModel.Swatches = new WpfObservableRangeCollection<Color>(builderInstance.Swatches);
  163. viewModel.Palette = new WpfObservableRangeCollection<Color>(builderInstance.Palette);
  164. AddMembers(viewModel.StructureRoot.GuidValue, builderInstance.Children);
  165. acc.AddFinishedActions(new DeleteRecordedChanges_Action());
  166. viewModel.MarkAsSaved();
  167. return viewModel;
  168. void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
  169. {
  170. acc.AddActions(
  171. new CreateStructureMember_Action(parentGuid, member.GuidValue, 0, member is DocumentViewModelBuilder.LayerBuilder ? StructureMemberType.Layer : StructureMemberType.Folder),
  172. new StructureMemberName_Action(member.GuidValue, member.Name)
  173. );
  174. if (!member.IsVisible)
  175. acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.GuidValue));
  176. if (member is DocumentViewModelBuilder.LayerBuilder layer)
  177. {
  178. PasteImage(member.GuidValue, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY, false);
  179. }
  180. acc.AddActions(
  181. new StructureMemberOpacity_Action(member.GuidValue, member.Opacity),
  182. new EndStructureMemberOpacity_Action());
  183. if (member.HasMask)
  184. {
  185. var maskSurface = member.Mask.Surface.Surface;
  186. acc.AddActions(new CreateStructureMemberMask_Action(member.GuidValue));
  187. if (!member.Mask.IsVisible)
  188. acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.GuidValue));
  189. PasteImage(member.GuidValue, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true);
  190. }
  191. acc.AddFinishedActions();
  192. if (member is DocumentViewModelBuilder.FolderBuilder { Children: not null } folder)
  193. {
  194. AddMembers(member.GuidValue, folder.Children);
  195. }
  196. }
  197. void PasteImage(Guid guid, DocumentViewModelBuilder.SurfaceBuilder surface, int width, int height, int offsetX, int offsetY, bool onMask)
  198. {
  199. acc.AddActions(
  200. new PasteImage_Action(surface.Surface, new(new RectD(new VecD(offsetX, offsetY), new(width, height))), guid, true, onMask),
  201. new EndPasteImage_Action());
  202. }
  203. void AddMembers(Guid parentGuid, IEnumerable<DocumentViewModelBuilder.StructureMemberBuilder> builders)
  204. {
  205. foreach (var child in builders.Reverse())
  206. {
  207. if (child.GuidValue == default)
  208. {
  209. child.GuidValue = Guid.NewGuid();
  210. }
  211. AddMember(parentGuid, child);
  212. }
  213. }
  214. }
  215. public void MarkAsSaved()
  216. {
  217. lastChangeOnSave = Internals.Tracker.LastChangeGuid;
  218. RaisePropertyChanged(nameof(AllChangesSaved));
  219. }
  220. public void MarkAsUnsaved()
  221. {
  222. lastChangeOnSave = Guid.NewGuid();
  223. RaisePropertyChanged(nameof(AllChangesSaved));
  224. }
  225. /// <summary>
  226. /// Takes the selected area and converts it into a surface
  227. /// </summary>
  228. /// <returns><see cref="Error"/> on error, <see cref="None"/> for empty <see cref="Surface"/>, <see cref="Surface"/> otherwise.</returns>
  229. public OneOf<Error, None, (Surface, RectI)> MaybeExtractSelectedArea(StructureMemberViewModel? layerToExtractFrom = null)
  230. {
  231. layerToExtractFrom ??= SelectedStructureMember;
  232. if (layerToExtractFrom is null || layerToExtractFrom is not LayerViewModel layerVm)
  233. return new Error();
  234. if (SelectionPathBindable.IsEmpty)
  235. return new None();
  236. IReadOnlyLayer? layer = (IReadOnlyLayer?)Internals.Tracker.Document.FindMember(layerVm.GuidValue);
  237. if (layer is null)
  238. return new Error();
  239. RectI bounds = (RectI)SelectionPathBindable.TightBounds;
  240. RectI? memberImageBounds;
  241. try
  242. {
  243. memberImageBounds = layer.LayerImage.FindLatestBounds();
  244. }
  245. catch (ObjectDisposedException)
  246. {
  247. return new Error();
  248. }
  249. if (memberImageBounds is null)
  250. return new None();
  251. bounds = bounds.Intersect(memberImageBounds.Value);
  252. bounds = bounds.Intersect(new RectI(VecI.Zero, SizeBindable));
  253. if (bounds.IsZeroOrNegativeArea)
  254. return new None();
  255. Surface output = new(bounds.Size);
  256. VectorPath clipPath = new VectorPath(SelectionPathBindable) { FillType = PathFillType.EvenOdd };
  257. clipPath.Transform(Matrix3X3.CreateTranslation(-bounds.X, -bounds.Y));
  258. output.DrawingSurface.Canvas.Save();
  259. output.DrawingSurface.Canvas.ClipPath(clipPath);
  260. try
  261. {
  262. layer.LayerImage.DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
  263. }
  264. catch (ObjectDisposedException)
  265. {
  266. output.Dispose();
  267. return new Error();
  268. }
  269. output.DrawingSurface.Canvas.Restore();
  270. return (output, bounds);
  271. }
  272. public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas)
  273. {
  274. if (scope == DocumentScope.SingleLayer && includeReference && includeCanvas)
  275. includeReference = false;
  276. if (includeCanvas && includeReference)
  277. {
  278. Color canvasColor = PickColorFromCanvas((VecI)pos, scope);
  279. Color? referenceColor = PickColorFromReferenceLayer(pos);
  280. if (referenceColor is null)
  281. return canvasColor;
  282. return ColorHelpers.BlendColors((Color)referenceColor, canvasColor);
  283. }
  284. if (includeCanvas)
  285. return PickColorFromCanvas((VecI)pos, scope);
  286. if (includeReference)
  287. return PickColorFromReferenceLayer(pos) ?? Colors.Transparent;
  288. return Colors.Transparent;
  289. }
  290. public Color? PickColorFromReferenceLayer(VecD pos)
  291. {
  292. WriteableBitmap? bitmap = ReferenceLayerViewModel.ReferenceBitmap;
  293. if (bitmap is null)
  294. return null;
  295. Matrix matrix = ReferenceLayerViewModel.ReferenceTransformMatrix;
  296. matrix.Invert();
  297. var transformed = matrix.Transform(new System.Windows.Point(pos.X, pos.Y));
  298. if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.Width || transformed.Y >= bitmap.Height)
  299. return null;
  300. return bitmap.GetPixel((int)transformed.X, (int)transformed.Y).ToColor();
  301. }
  302. public Color PickColorFromCanvas(VecI pos, DocumentScope scope)
  303. {
  304. // there is a tiny chance that the image might get disposed by another thread
  305. try
  306. {
  307. // it might've been a better idea to implement this function asynchronously
  308. // via a passthrough action to avoid all the try catches
  309. if (scope == DocumentScope.AllLayers)
  310. {
  311. VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
  312. return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full, Internals.Tracker.Document.StructureRoot)
  313. .Match<Color>(
  314. (Chunk chunk) =>
  315. {
  316. VecI posOnChunk = pos - chunkPos * ChunkyImage.FullChunkSize;
  317. var color = chunk.Surface.GetSRGBPixel(posOnChunk);
  318. chunk.Dispose();
  319. return color;
  320. },
  321. _ => Colors.Transparent
  322. );
  323. }
  324. if (SelectedStructureMember is not LayerViewModel layerVm)
  325. return Colors.Transparent;
  326. IReadOnlyStructureMember? maybeMember = Internals.Tracker.Document.FindMember(layerVm.GuidValue);
  327. if (maybeMember is not IReadOnlyLayer layer)
  328. return Colors.Transparent;
  329. return layer.LayerImage.GetMostUpToDatePixel(pos);
  330. }
  331. catch (ObjectDisposedException)
  332. {
  333. return Colors.Transparent;
  334. }
  335. }
  336. #region Internal Methods
  337. // these are intended to only be called from DocumentUpdater
  338. public void InternalRaiseLayersChanged(LayersChangedEventArgs args) => LayersChanged?.Invoke(this, args);
  339. public void InternalRaiseSizeChanged(DocumentSizeChangedEventArgs args) => SizeChanged?.Invoke(this, args);
  340. public void InternalSetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
  341. {
  342. this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
  343. RaisePropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
  344. }
  345. public void InternalSetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
  346. {
  347. this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
  348. RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
  349. }
  350. public void InternalSetVerticalSymmetryAxisX(int verticalSymmetryAxisX)
  351. {
  352. this.verticalSymmetryAxisX = verticalSymmetryAxisX;
  353. RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
  354. }
  355. public void InternalSetHorizontalSymmetryAxisY(int horizontalSymmetryAxisY)
  356. {
  357. this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
  358. RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
  359. }
  360. public void InternalSetSize(VecI size)
  361. {
  362. this.size = size;
  363. RaisePropertyChanged(nameof(SizeBindable));
  364. RaisePropertyChanged(nameof(Width));
  365. RaisePropertyChanged(nameof(Height));
  366. }
  367. public void InternalUpdateSelectionPath(VectorPath vectorPath)
  368. {
  369. (VectorPath? toDispose, this.selectionPath) = (this.selectionPath, vectorPath);
  370. toDispose.Dispose();
  371. RaisePropertyChanged(nameof(SelectionPathBindable));
  372. }
  373. public void InternalSetSelectedMember(StructureMemberViewModel? member)
  374. {
  375. SelectedStructureMember = member;
  376. RaisePropertyChanged(nameof(SelectedStructureMember));
  377. }
  378. public void InternalClearSoftSelectedMembers() => softSelectedStructureMembers.Clear();
  379. public void InternalAddSoftSelectedMember(StructureMemberViewModel member) => softSelectedStructureMembers.Add(member);
  380. public void InternalRemoveSoftSelectedMember(StructureMemberViewModel member) => softSelectedStructureMembers.Remove(member);
  381. #endregion
  382. }