Document.cs 19 KB

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