Document.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. using System;
  2. using System.Buffers;
  3. using System.Collections.ObjectModel;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Windows;
  7. using System.Windows.Media;
  8. using System.Windows.Media.Imaging;
  9. using PixiEditor.Helpers;
  10. using PixiEditor.Models.Controllers;
  11. using PixiEditor.Models.Enums;
  12. using PixiEditor.Models.IO;
  13. using PixiEditor.Models.Layers;
  14. using PixiEditor.Models.Position;
  15. using PixiEditor.Models.Undo;
  16. using PixiEditor.ViewModels;
  17. namespace PixiEditor.Models.DataHolders
  18. {
  19. public class Document : NotifyableObject
  20. {
  21. private int activeLayerIndex;
  22. private int height;
  23. private int width;
  24. public Document(int width, int height)
  25. {
  26. Width = width;
  27. Height = height;
  28. RequestCloseDocumentCommand = new RelayCommand(RequestCloseDocument);
  29. SetAsActiveOnClickCommand = new RelayCommand(SetAsActiveOnClick);
  30. UndoManager = new UndoManager();
  31. XamlAccesibleViewModel = ViewModelMain.Current ?? null;
  32. GeneratePreviewLayer();
  33. DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(0, 0, width, height));
  34. }
  35. public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
  36. public event EventHandler<LayersChangedEventArgs> LayersChanged;
  37. public RelayCommand RequestCloseDocumentCommand { get; set; }
  38. public RelayCommand SetAsActiveOnClickCommand { get; set; }
  39. private ViewModelMain xamlAccesibleViewModel = null;
  40. public ViewModelMain XamlAccesibleViewModel // Used to access ViewModelMain, without changing DataContext in XAML
  41. {
  42. get => xamlAccesibleViewModel;
  43. set
  44. {
  45. xamlAccesibleViewModel = value;
  46. RaisePropertyChanged(nameof(XamlAccesibleViewModel));
  47. }
  48. }
  49. private string documentFilePath = string.Empty;
  50. public string DocumentFilePath
  51. {
  52. get => documentFilePath;
  53. set
  54. {
  55. documentFilePath = value;
  56. RaisePropertyChanged(nameof(DocumentFilePath));
  57. RaisePropertyChanged(nameof(Name));
  58. }
  59. }
  60. private bool changesSaved = true;
  61. public bool ChangesSaved
  62. {
  63. get => changesSaved;
  64. set
  65. {
  66. changesSaved = value;
  67. RaisePropertyChanged(nameof(ChangesSaved));
  68. RaisePropertyChanged(nameof(Name)); // This updates name so it shows asterisk if unsaved
  69. }
  70. }
  71. public string Name
  72. {
  73. get => (string.IsNullOrEmpty(DocumentFilePath) ? "Untitled" : Path.GetFileName(DocumentFilePath))
  74. + (!ChangesSaved ? " *" : string.Empty);
  75. }
  76. public int Width
  77. {
  78. get => width;
  79. set
  80. {
  81. width = value;
  82. RaisePropertyChanged("Width");
  83. }
  84. }
  85. public int Height
  86. {
  87. get => height;
  88. set
  89. {
  90. height = value;
  91. RaisePropertyChanged("Height");
  92. }
  93. }
  94. private Selection selection = new Selection(Array.Empty<Coordinates>());
  95. public Selection ActiveSelection
  96. {
  97. get => selection;
  98. set
  99. {
  100. selection = value;
  101. RaisePropertyChanged("ActiveSelection");
  102. }
  103. }
  104. private Layer previewLayer;
  105. public Layer PreviewLayer
  106. {
  107. get => previewLayer;
  108. set
  109. {
  110. previewLayer = value;
  111. RaisePropertyChanged("PreviewLayer");
  112. }
  113. }
  114. private double mouseXonCanvas;
  115. private double mouseYonCanvas;
  116. public double MouseXOnCanvas // Mouse X coordinate relative to canvas
  117. {
  118. get => mouseXonCanvas;
  119. set
  120. {
  121. mouseXonCanvas = value;
  122. RaisePropertyChanged(nameof(MouseXOnCanvas));
  123. }
  124. }
  125. public double MouseYOnCanvas // Mouse Y coordinate relative to canvas
  126. {
  127. get => mouseYonCanvas;
  128. set
  129. {
  130. mouseYonCanvas = value;
  131. RaisePropertyChanged(nameof(MouseYOnCanvas));
  132. }
  133. }
  134. private double zoomPercentage = 100;
  135. public double ZoomPercentage
  136. {
  137. get => zoomPercentage;
  138. set
  139. {
  140. zoomPercentage = value;
  141. RaisePropertyChanged(nameof(ZoomPercentage));
  142. }
  143. }
  144. private Point viewPortPosition;
  145. public Point ViewportPosition
  146. {
  147. get => viewPortPosition;
  148. set
  149. {
  150. viewPortPosition = value;
  151. RaisePropertyChanged(nameof(ViewportPosition));
  152. }
  153. }
  154. private bool recenterZoombox = true;
  155. public bool RecenterZoombox
  156. {
  157. get => recenterZoombox;
  158. set
  159. {
  160. recenterZoombox = value;
  161. RaisePropertyChanged(nameof(RecenterZoombox));
  162. }
  163. }
  164. public UndoManager UndoManager { get; set; }
  165. public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
  166. public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
  167. public int ActiveLayerIndex
  168. {
  169. get => activeLayerIndex;
  170. set
  171. {
  172. activeLayerIndex = value;
  173. RaisePropertyChanged("ActiveLayerIndex");
  174. RaisePropertyChanged("ActiveLayer");
  175. }
  176. }
  177. public void GeneratePreviewLayer()
  178. {
  179. PreviewLayer = new Layer("_previewLayer")
  180. {
  181. MaxWidth = Width,
  182. MaxHeight = Height
  183. };
  184. }
  185. public void CenterViewport()
  186. {
  187. RecenterZoombox = false; // It's a trick to trigger change in UserControl
  188. RecenterZoombox = true;
  189. ViewportPosition = default;
  190. ZoomPercentage = default;
  191. }
  192. public void SaveWithDialog()
  193. {
  194. bool savedSuccessfully = Exporter.SaveAsEditableFileWithDialog(this, out string path);
  195. DocumentFilePath = path;
  196. ChangesSaved = savedSuccessfully;
  197. }
  198. public void Save()
  199. {
  200. Save(DocumentFilePath);
  201. }
  202. public void Save(string path)
  203. {
  204. DocumentFilePath = Exporter.SaveAsEditableFile(this, path);
  205. ChangesSaved = true;
  206. }
  207. public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
  208. /// <summary>
  209. /// Resizes canvas to specified width and height to selected anchor.
  210. /// </summary>
  211. /// <param name="width">New width of canvas.</param>
  212. /// <param name="height">New height of canvas.</param>
  213. /// <param name="anchor">
  214. /// Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
  215. /// vertical.
  216. /// </param>
  217. public void ResizeCanvas(int width, int height, AnchorPoint anchor)
  218. {
  219. int oldWidth = Width;
  220. int oldHeight = Height;
  221. int offsetX = GetOffsetXForAnchor(Width, width, anchor);
  222. int offsetY = GetOffsetYForAnchor(Height, height, anchor);
  223. Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
  224. Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
  225. .ToArray();
  226. object[] processArgs = { newOffsets, width, height };
  227. object[] reverseProcessArgs = { oldOffsets, Width, Height };
  228. ResizeCanvas(newOffsets, width, height);
  229. UndoManager.AddUndoChange(new Change(
  230. ResizeCanvasProcess,
  231. reverseProcessArgs,
  232. ResizeCanvasProcess,
  233. processArgs,
  234. "Resize canvas"));
  235. DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
  236. }
  237. public void SetActiveLayer(int index)
  238. {
  239. if (ActiveLayerIndex <= Layers.Count - 1)
  240. {
  241. ActiveLayer.IsActive = false;
  242. }
  243. if (Layers.Any(x => x.IsActive))
  244. {
  245. Layers.First(x => x.IsActive).IsActive = false;
  246. }
  247. ActiveLayerIndex = index;
  248. ActiveLayer.IsActive = true;
  249. LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
  250. }
  251. public void MoveLayerIndexBy(int layerIndex, int amount)
  252. {
  253. Layers.Move(layerIndex, layerIndex + amount);
  254. if (ActiveLayerIndex == layerIndex)
  255. {
  256. SetActiveLayer(layerIndex + amount);
  257. }
  258. UndoManager.AddUndoChange(new Change(
  259. MoveLayerProcess,
  260. new object[] { layerIndex + amount, -amount },
  261. MoveLayerProcess,
  262. new object[] { layerIndex, amount },
  263. "Move layer"));
  264. }
  265. public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
  266. {
  267. AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
  268. Layers.Last().LayerBitmap = bitmap;
  269. }
  270. public void AddNewLayer(string name, bool setAsActive = true)
  271. {
  272. AddNewLayer(name, 0, 0, setAsActive);
  273. }
  274. public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
  275. {
  276. Layers.Add(new Layer(name, width, height)
  277. {
  278. MaxHeight = Height,
  279. MaxWidth = Width
  280. });
  281. if (setAsActive)
  282. {
  283. SetActiveLayer(Layers.Count - 1);
  284. }
  285. if (Layers.Count > 1)
  286. {
  287. StorageBasedChange storageChange = new StorageBasedChange(this, new[] { Layers[^1] });
  288. UndoManager.AddUndoChange(
  289. storageChange.ToChange(
  290. RemoveLayerProcess,
  291. new object[] { Layers[^1].LayerGuid },
  292. RestoreLayerProcess,
  293. new object[] { new Layer[] { Layers[^1] }, storageChange.StoredLayers },
  294. "Add layer"));
  295. }
  296. LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
  297. }
  298. public void SetNextLayerAsActive(int lastLayerIndex)
  299. {
  300. if (Layers.Count > 0)
  301. {
  302. if (lastLayerIndex == 0)
  303. {
  304. SetActiveLayer(0);
  305. }
  306. else
  307. {
  308. SetActiveLayer(lastLayerIndex - 1);
  309. }
  310. }
  311. }
  312. public void RemoveLayer(int layerIndex)
  313. {
  314. if (Layers.Count == 0)
  315. {
  316. return;
  317. }
  318. bool wasActive = Layers[layerIndex].IsActive;
  319. StorageBasedChange change = new StorageBasedChange(this, new[] { Layers[layerIndex] });
  320. UndoManager.AddUndoChange(
  321. change.ToChange(RestoreLayersProcess, RemoveLayerProcess, new object[] { Layers[layerIndex].LayerGuid }, "Remove layer"));
  322. Layers.RemoveAt(layerIndex);
  323. if (wasActive)
  324. {
  325. SetNextLayerAsActive(layerIndex);
  326. }
  327. }
  328. private void MoveLayerProcess(object[] parameter)
  329. {
  330. int layerIndex = (int)parameter[0];
  331. int amount = (int)parameter[1];
  332. MoveLayerIndexBy(layerIndex, amount);
  333. }
  334. private void RestoreLayerProcess(object[] parameters)
  335. {
  336. RestoreLayersProcess((Layer[])parameters[0], (UndoLayer[])parameters[1]);
  337. }
  338. private void RestoreLayersProcess(Layer[] layers, UndoLayer[] layersData)
  339. {
  340. for (int i = 0; i < layers.Length; i++)
  341. {
  342. Layer layer = layers[i];
  343. Layers.Insert(layersData[i].LayerIndex, layer);
  344. if (layer.IsActive)
  345. {
  346. SetActiveLayer(Layers.IndexOf(layer));
  347. }
  348. }
  349. }
  350. private void RemoveLayerProcess(object[] parameters)
  351. {
  352. if (parameters != null && parameters.Length > 0 && parameters[0] is Guid layerGuid)
  353. {
  354. Layer layer = Layers.First(x => x.LayerGuid == layerGuid);
  355. int index = Layers.IndexOf(layer);
  356. bool wasActive = layer.IsActive;
  357. Layers.Remove(layer);
  358. if (wasActive)
  359. {
  360. SetNextLayerAsActive(index);
  361. }
  362. }
  363. }
  364. /// <summary>
  365. /// Resizes all document layers using NearestNeighbor interpolation.
  366. /// </summary>
  367. /// <param name="newWidth">New document width.</param>
  368. /// <param name="newHeight">New document height.</param>
  369. public void Resize(int newWidth, int newHeight)
  370. {
  371. object[] reverseArgs = { Width, Height };
  372. object[] args = { newWidth, newHeight };
  373. ResizeDocument(args);
  374. UndoManager.AddUndoChange(new Change(
  375. ResizeDocument,
  376. reverseArgs,
  377. ResizeDocument,
  378. args,
  379. "Resize document"));
  380. }
  381. /// <summary>
  382. /// Resizes canvas, so it fits exactly the size of drawn content, without any transparent pixels outside.
  383. /// </summary>
  384. public void ClipCanvas()
  385. {
  386. DoubleCords points = GetEdgePoints();
  387. int smallestX = points.Coords1.X;
  388. int smallestY = points.Coords1.Y;
  389. int biggestX = points.Coords2.X;
  390. int biggestY = points.Coords2.Y;
  391. if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
  392. {
  393. return;
  394. }
  395. int width = biggestX - smallestX;
  396. int height = biggestY - smallestY;
  397. Coordinates moveVector = new Coordinates(-smallestX, -smallestY);
  398. Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
  399. int oldWidth = Width;
  400. int oldHeight = Height;
  401. MoveOffsets(moveVector);
  402. Width = width;
  403. Height = height;
  404. object[] reverseArguments = { oldOffsets, oldWidth, oldHeight };
  405. object[] processArguments = { Layers.Select(x => x.Offset).ToArray(), width, height };
  406. UndoManager.AddUndoChange(new Change(
  407. ResizeCanvasProcess,
  408. reverseArguments,
  409. ResizeCanvasProcess,
  410. processArguments,
  411. "Clip canvas"));
  412. }
  413. /// <summary>
  414. /// Centers content inside document.
  415. /// </summary>
  416. public void CenterContent()
  417. {
  418. DoubleCords points = GetEdgePoints();
  419. int smallestX = points.Coords1.X;
  420. int smallestY = points.Coords1.Y;
  421. int biggestX = points.Coords2.X;
  422. int biggestY = points.Coords2.Y;
  423. if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
  424. {
  425. return;
  426. }
  427. Coordinates contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
  428. Coordinates documentCenter = CoordinatesCalculator.GetCenterPoint(
  429. new Coordinates(0, 0),
  430. new Coordinates(Width, Height));
  431. Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
  432. MoveOffsets(moveVector);
  433. UndoManager.AddUndoChange(
  434. new Change(
  435. MoveOffsetsProcess,
  436. new object[] { new Coordinates(-moveVector.X, -moveVector.Y) },
  437. MoveOffsetsProcess,
  438. new object[] { moveVector },
  439. "Center content"));
  440. }
  441. private void SetAsActiveOnClick(object obj)
  442. {
  443. XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
  444. XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
  445. XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
  446. }
  447. private void RequestCloseDocument(object parameter)
  448. {
  449. ViewModelMain.Current.DocumentSubViewModel.RequestCloseDocument(this);
  450. }
  451. private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
  452. {
  453. if (anchor.HasFlag(AnchorPoint.Center))
  454. {
  455. return Math.Abs((destWidth / 2) - (srcWidth / 2));
  456. }
  457. if (anchor.HasFlag(AnchorPoint.Right))
  458. {
  459. return Math.Abs(destWidth - srcWidth);
  460. }
  461. return 0;
  462. }
  463. private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
  464. {
  465. if (anchor.HasFlag(AnchorPoint.Middle))
  466. {
  467. return Math.Abs((destHeight / 2) - (srcHeight / 2));
  468. }
  469. if (anchor.HasFlag(AnchorPoint.Bottom))
  470. {
  471. return Math.Abs(destHeight - srcHeight);
  472. }
  473. return 0;
  474. }
  475. private void ResizeDocument(object[] arguments)
  476. {
  477. int oldWidth = Width;
  478. int oldHeight = Height;
  479. int newWidth = (int)arguments[0];
  480. int newHeight = (int)arguments[1];
  481. for (int i = 0; i < Layers.Count; i++)
  482. {
  483. float widthRatio = (float)newWidth / Width;
  484. float heightRatio = (float)newHeight / Height;
  485. int layerWidth = (int)(Layers[i].Width * widthRatio);
  486. int layerHeight = (int)(Layers[i].Height * heightRatio);
  487. Layers[i].Resize(layerWidth, layerHeight, newWidth, newHeight);
  488. Layers[i].Offset = new Thickness(Layers[i].OffsetX * widthRatio, Layers[i].OffsetY * heightRatio, 0, 0);
  489. }
  490. Height = newHeight;
  491. Width = newWidth;
  492. DocumentSizeChanged?.Invoke(
  493. this,
  494. new DocumentSizeChangedEventArgs(oldWidth, oldHeight, newWidth, newHeight));
  495. }
  496. private void ResizeCanvasProcess(object[] arguments)
  497. {
  498. int oldWidth = Width;
  499. int oldHeight = Height;
  500. Thickness[] offset = (Thickness[])arguments[0];
  501. int width = (int)arguments[1];
  502. int height = (int)arguments[2];
  503. ResizeCanvas(offset, width, height);
  504. DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
  505. }
  506. /// <summary>
  507. /// Resizes canvas.
  508. /// </summary>
  509. /// <param name="offset">Offset of content in new canvas. It will move layer to that offset.</param>
  510. /// <param name="newWidth">New canvas size.</param>
  511. /// <param name="newHeight">New canvas height.</param>
  512. private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
  513. {
  514. for (int i = 0; i < Layers.Count; i++)
  515. {
  516. Layers[i].Offset = offset[i];
  517. Layers[i].MaxWidth = newWidth;
  518. Layers[i].MaxHeight = newHeight;
  519. }
  520. Width = newWidth;
  521. Height = newHeight;
  522. }
  523. private DoubleCords GetEdgePoints()
  524. {
  525. Layer firstLayer = Layers[0];
  526. int smallestX = firstLayer.OffsetX;
  527. int smallestY = firstLayer.OffsetY;
  528. int biggestX = smallestX + firstLayer.Width;
  529. int biggestY = smallestY + firstLayer.Height;
  530. for (int i = 0; i < Layers.Count; i++)
  531. {
  532. Layers[i].ClipCanvas();
  533. if (Layers[i].OffsetX < smallestX)
  534. {
  535. smallestX = Layers[i].OffsetX;
  536. }
  537. if (Layers[i].OffsetX + Layers[i].Width > biggestX)
  538. {
  539. biggestX = Layers[i].OffsetX + Layers[i].Width;
  540. }
  541. if (Layers[i].OffsetY < smallestY)
  542. {
  543. smallestY = Layers[i].OffsetY;
  544. }
  545. if (Layers[i].OffsetY + Layers[i].Height > biggestY)
  546. {
  547. biggestY = Layers[i].OffsetY + Layers[i].Height;
  548. }
  549. }
  550. return new DoubleCords(
  551. new Coordinates(smallestX, smallestY),
  552. new Coordinates(biggestX, biggestY));
  553. }
  554. /// <summary>
  555. /// Moves offsets of layers by specified vector.
  556. /// </summary>
  557. private void MoveOffsets(Coordinates moveVector)
  558. {
  559. for (int i = 0; i < Layers.Count; i++)
  560. {
  561. Thickness offset = Layers[i].Offset;
  562. Layers[i].Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
  563. }
  564. }
  565. private void MoveOffsetsProcess(object[] arguments)
  566. {
  567. Coordinates vector = (Coordinates)arguments[0];
  568. MoveOffsets(vector);
  569. }
  570. }
  571. }