2
0

Layer.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. using PixiEditor.Helpers.Extensions;
  2. using PixiEditor.Models.DataHolders;
  3. using PixiEditor.Models.Position;
  4. using PixiEditor.Models.Undo;
  5. using PixiEditor.ViewModels;
  6. using SkiaSharp;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Diagnostics;
  10. using System.Linq;
  11. using System.Windows;
  12. namespace PixiEditor.Models.Layers
  13. {
  14. [DebuggerDisplay("'{name,nq}' {width}x{height}")]
  15. public class Layer : BasicLayer
  16. {
  17. private bool clipRequested;
  18. private bool isActive;
  19. private bool isRenaming;
  20. private bool isVisible = true;
  21. private Surface layerBitmap;
  22. private string name;
  23. private Thickness offset;
  24. private float opacity = 1f;
  25. private string layerHighlightColor = "#666666";
  26. public Layer(string name)
  27. {
  28. Name = name;
  29. LayerBitmap = new Surface(1, 1);
  30. IsReset = true;
  31. Width = 1;
  32. Height = 1;
  33. GuidValue = Guid.NewGuid();
  34. }
  35. public Layer(string name, int width, int height)
  36. {
  37. Name = name;
  38. LayerBitmap = new Surface(width, height);
  39. IsReset = true;
  40. Width = width;
  41. Height = height;
  42. GuidValue = Guid.NewGuid();
  43. }
  44. public Layer(string name, Surface layerBitmap)
  45. {
  46. Name = name;
  47. LayerBitmap = layerBitmap;
  48. Width = layerBitmap.Width;
  49. Height = layerBitmap.Height;
  50. GuidValue = Guid.NewGuid();
  51. }
  52. public Dictionary<Coordinates, SKColor> LastRelativeCoordinates { get; set; }
  53. public string LayerHighlightColor
  54. {
  55. get => IsActive ? layerHighlightColor : "#00000000";
  56. set
  57. {
  58. SetProperty(ref layerHighlightColor, value);
  59. }
  60. }
  61. public string Name
  62. {
  63. get => name;
  64. set
  65. {
  66. name = value;
  67. RaisePropertyChanged(nameof(Name));
  68. }
  69. }
  70. public bool IsActive
  71. {
  72. get => isActive;
  73. set
  74. {
  75. isActive = value;
  76. RaisePropertyChanged(nameof(IsActive));
  77. RaisePropertyChanged(nameof(LayerHighlightColor));
  78. }
  79. }
  80. public bool IsVisible
  81. {
  82. get => isVisible;
  83. set
  84. {
  85. isVisible = value;
  86. RaisePropertyChanged(nameof(IsVisibleUndoTriggerable));
  87. RaisePropertyChanged(nameof(IsVisible));
  88. ViewModelMain.Current?.ToolsSubViewModel?.TriggerCacheOutdated();
  89. InvokeLayerBitmapChange();
  90. }
  91. }
  92. public bool IsVisibleUndoTriggerable
  93. {
  94. get => IsVisible;
  95. set
  96. {
  97. if (value != IsVisible)
  98. {
  99. ViewModelMain.Current?.BitmapManager?.ActiveDocument?.UndoManager
  100. .AddUndoChange(
  101. new Change(
  102. nameof(IsVisible),
  103. isVisible,
  104. value,
  105. LayerHelper.FindLayerByGuidProcess,
  106. new object[] { GuidValue },
  107. "Change layer visibility"));
  108. IsVisible = value;
  109. InvokeLayerBitmapChange();
  110. }
  111. }
  112. }
  113. public bool IsRenaming
  114. {
  115. get => isRenaming;
  116. set
  117. {
  118. isRenaming = value;
  119. RaisePropertyChanged("IsRenaming");
  120. }
  121. }
  122. public Surface LayerBitmap
  123. {
  124. get => layerBitmap;
  125. set
  126. {
  127. Int32Rect prevRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
  128. layerBitmap = value;
  129. Width = layerBitmap.Width;
  130. Height = layerBitmap.Height;
  131. Int32Rect curRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
  132. RaisePropertyChanged(nameof(LayerBitmap));
  133. InvokeLayerBitmapChange(prevRect.Expand(curRect));
  134. }
  135. }
  136. public float Opacity
  137. {
  138. get => opacity;
  139. set
  140. {
  141. opacity = value;
  142. RaisePropertyChanged(nameof(OpacityUndoTriggerable));
  143. ViewModelMain.Current?.ToolsSubViewModel?.TriggerCacheOutdated();
  144. InvokeLayerBitmapChange();
  145. }
  146. }
  147. public float OpacityUndoTriggerable
  148. {
  149. get => Opacity;
  150. set
  151. {
  152. if (value != Opacity)
  153. {
  154. ViewModelMain.Current?.BitmapManager?.ActiveDocument?.UndoManager
  155. .AddUndoChange(
  156. new Change(
  157. nameof(Opacity),
  158. opacity,
  159. value,
  160. LayerHelper.FindLayerByGuidProcess,
  161. new object[] { GuidValue },
  162. "Change layer opacity"));
  163. Opacity = value;
  164. }
  165. }
  166. }
  167. public int OffsetX => (int)Offset.Left;
  168. public int OffsetY => (int)Offset.Top;
  169. public Thickness Offset
  170. {
  171. get => offset;
  172. set
  173. {
  174. Int32Rect prevRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
  175. offset = value;
  176. Int32Rect curRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
  177. RaisePropertyChanged(nameof(Offset));
  178. InvokeLayerBitmapChange(prevRect.Expand(curRect));
  179. }
  180. }
  181. public int MaxWidth { get; set; } = int.MaxValue;
  182. public int MaxHeight { get; set; } = int.MaxValue;
  183. public bool IsReset { get; private set; }
  184. public event EventHandler<Int32Rect> LayerBitmapChanged;
  185. public void InvokeLayerBitmapChange()
  186. {
  187. IsReset = false;
  188. LayerBitmapChanged?.Invoke(this, new Int32Rect(OffsetX, OffsetY, Width, Height));
  189. }
  190. public void InvokeLayerBitmapChange(Int32Rect dirtyArea)
  191. {
  192. IsReset = false;
  193. LayerBitmapChanged?.Invoke(this, dirtyArea);
  194. }
  195. /// <summary>
  196. /// Changes Guid of layer.
  197. /// </summary>
  198. /// <param name="newGuid">Guid to set.</param>
  199. /// <remarks>This is potentially destructive operation, use when absolutelly necessary.</remarks>
  200. public void ChangeGuid(Guid newGuid)
  201. {
  202. GuidValue = newGuid;
  203. }
  204. public IEnumerable<Layer> GetLayers()
  205. {
  206. return new Layer[] { this };
  207. }
  208. /// <summary>
  209. /// Returns clone of layer.
  210. /// </summary>
  211. public Layer Clone(bool generateNewGuid = false)
  212. {
  213. return new Layer(Name, new Surface(LayerBitmap))
  214. {
  215. IsVisible = IsVisible,
  216. Offset = Offset,
  217. MaxHeight = MaxHeight,
  218. MaxWidth = MaxWidth,
  219. Opacity = Opacity,
  220. IsActive = IsActive,
  221. IsRenaming = IsRenaming,
  222. GuidValue = generateNewGuid ? Guid.NewGuid() : GuidValue
  223. };
  224. }
  225. public void RaisePropertyChange(string property)
  226. {
  227. RaisePropertyChanged(property);
  228. }
  229. /// <summary>
  230. /// Resizes bitmap with it's content using NearestNeighbor interpolation.
  231. /// </summary>
  232. /// <param name="width">New width.</param>
  233. /// <param name="height">New height.</param>
  234. /// <param name="newMaxWidth">New layer maximum width, this should be document width.</param>
  235. /// <param name="newMaxHeight">New layer maximum height, this should be document height.</param>
  236. public void Resize(int width, int height, int newMaxWidth, int newMaxHeight)
  237. {
  238. LayerBitmap = LayerBitmap.ResizeNearestNeighbor(width, height);
  239. Width = width;
  240. Height = height;
  241. MaxWidth = newMaxWidth;
  242. MaxHeight = newMaxHeight;
  243. }
  244. /// <summary>
  245. /// Converts coordinates relative to viewport to relative to layer.
  246. /// </summary>
  247. public Coordinates GetRelativePosition(Coordinates cords)
  248. {
  249. return new Coordinates(cords.X - OffsetX, cords.Y - OffsetY);
  250. }
  251. /// <summary>
  252. /// Returns pixel color of x and y coordinates relative to document using (x - OffsetX) formula.
  253. /// </summary>
  254. /// <param name="x">Viewport relative X.</param>
  255. /// <param name="y">Viewport relative Y.</param>
  256. /// <returns>Color of a pixel.</returns>
  257. public SKColor GetPixelWithOffset(int x, int y)
  258. {
  259. //This does not use GetRelativePosition for better performance
  260. return GetPixel(x - OffsetX, y - OffsetY);
  261. }
  262. /// <summary>
  263. /// Returns pixel color on x and y.
  264. /// </summary>
  265. /// <param name="x">X coordinate.</param>
  266. /// <param name="y">Y Coordinate.</param>
  267. /// <returns>Color of pixel, if out of bounds, returns transparent pixel.</returns>
  268. public SKColor GetPixel(int x, int y)
  269. {
  270. if (x > Width - 1 || x < 0 || y > Height - 1 || y < 0)
  271. {
  272. return SKColors.Empty;
  273. }
  274. return LayerBitmap.GetSRGBPixel(x, y);
  275. }
  276. public void SetPixelWithOffset(Coordinates coordinates, SKColor color)
  277. {
  278. LayerBitmap.SetSRGBPixel(coordinates.X - OffsetX, coordinates.Y - OffsetY, color);
  279. }
  280. public void SetPixelWithOffset(int x, int y, SKColor color)
  281. {
  282. LayerBitmap.SetSRGBPixel(x - OffsetX, y - OffsetY, color);
  283. }
  284. /// <summary>
  285. /// Applies pixels to layer.
  286. /// </summary>
  287. /// <param name="pixels">Pixels to apply.</param>
  288. /// <param name="dynamicResize">Resizes bitmap to fit content.</param>
  289. /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap.</param>
  290. public void SetPixels(BitmapPixelChanges pixels, bool dynamicResize = true, bool applyOffset = true)
  291. {
  292. if (pixels.ChangedPixels == null || pixels.ChangedPixels.Count == 0)
  293. {
  294. return;
  295. }
  296. if (applyOffset)
  297. {
  298. pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
  299. }
  300. if (dynamicResize)
  301. {
  302. DynamicResize(pixels);
  303. }
  304. LastRelativeCoordinates = pixels.ChangedPixels;
  305. int minX = int.MaxValue;
  306. int maxX = int.MinValue;
  307. int minY = int.MaxValue;
  308. int maxY = int.MinValue;
  309. foreach (KeyValuePair<Coordinates, SKColor> coords in pixels.ChangedPixels)
  310. {
  311. if (OutOfBounds(coords.Key))
  312. {
  313. continue;
  314. }
  315. LayerBitmap.SetSRGBPixel(coords.Key.X, coords.Key.Y, coords.Value);
  316. minX = Math.Min(minX, coords.Key.X);
  317. minY = Math.Min(minY, coords.Key.Y);
  318. maxX = Math.Max(maxX, coords.Key.X);
  319. maxY = Math.Max(maxY, coords.Key.Y);
  320. }
  321. ClipIfNecessary();
  322. if (minX != int.MaxValue)
  323. InvokeLayerBitmapChange(new Int32Rect(minX + OffsetX, minY + OffsetY, maxX - minX + 1, maxY - minY + 1));
  324. }
  325. /// <summary>
  326. /// Converts absolute coordinates array to relative to this layer coordinates array.
  327. /// </summary>
  328. /// <param name="nonRelativeCords">absolute coordinates array.</param>
  329. public Coordinates[] ConvertToRelativeCoordinates(Coordinates[] nonRelativeCords)
  330. {
  331. Coordinates[] result = new Coordinates[nonRelativeCords.Length];
  332. for (int i = 0; i < nonRelativeCords.Length; i++)
  333. {
  334. result[i] = new Coordinates(nonRelativeCords[i].X - OffsetX, nonRelativeCords[i].Y - OffsetY);
  335. }
  336. return result;
  337. }
  338. public void CreateNewBitmap(int width, int height)
  339. {
  340. LayerBitmap = new Surface(width, height);
  341. Width = width;
  342. Height = height;
  343. }
  344. /// <summary>
  345. /// Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth.
  346. /// </summary>
  347. public void DynamicResize(BitmapPixelChanges pixels)
  348. {
  349. if (pixels.ChangedPixels.Count == 0)
  350. {
  351. return;
  352. }
  353. ResetOffset(pixels);
  354. Tuple<DoubleCoords, bool> borderData = ExtractBorderData(pixels);
  355. DoubleCoords minMaxCords = borderData.Item1;
  356. int newMaxX = minMaxCords.Coords2.X;
  357. int newMaxY = minMaxCords.Coords2.Y;
  358. int newMinX = minMaxCords.Coords1.X;
  359. int newMinY = minMaxCords.Coords1.Y;
  360. if (!(pixels.WasBuiltAsSingleColored && pixels.ChangedPixels.First().Value.Alpha == 0))
  361. {
  362. DynamicResizeRelative(newMaxX, newMaxY, newMinX, newMinY);
  363. }
  364. // if clip is requested
  365. if (borderData.Item2)
  366. {
  367. clipRequested = true;
  368. }
  369. }
  370. public void DynamicResizeAbsolute(Int32Rect newSize)
  371. {
  372. newSize = newSize.Intersect(new Int32Rect(0, 0, MaxWidth, MaxHeight));
  373. if (newSize.IsEmpty)
  374. return;
  375. if (IsReset)
  376. {
  377. Offset = new Thickness(newSize.X, newSize.Y, 0, 0);
  378. }
  379. int relX = newSize.X - OffsetX;
  380. int relY = newSize.Y - OffsetY;
  381. int maxX = relX + newSize.Width - 1;
  382. int maxY = relY + newSize.Height - 1;
  383. DynamicResizeRelative(maxX, maxY, relX, relY);
  384. }
  385. /// <summary>
  386. /// Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth.
  387. /// </summary>
  388. public void DynamicResizeRelative(int newMaxX, int newMaxY, int newMinX, int newMinY)
  389. {
  390. if ((newMaxX + 1 > Width && Width < MaxWidth) || (newMaxY + 1 > Height && Height < MaxHeight))
  391. {
  392. newMaxX = Math.Max(newMaxX, (int)(Width * 1.5f));
  393. newMaxY = Math.Max(newMaxY, (int)(Height * 1.5f));
  394. IncreaseSizeToBottomAndRight(newMaxX, newMaxY);
  395. }
  396. if ((newMinX < 0 && Width < MaxWidth) || (newMinY < 0 && Height < MaxHeight))
  397. {
  398. newMinX = Math.Min(newMinX, Width - (int)(Width * 1.5f));
  399. newMinY = Math.Min(newMinY, Height - (int)(Height * 1.5f));
  400. IncreaseSizeToTopAndLeft(newMinX, newMinY);
  401. }
  402. }
  403. public Int32Rect GetContentDimensions()
  404. {
  405. DoubleCoords points = GetEdgePoints();
  406. int smallestX = points.Coords1.X;
  407. int smallestY = points.Coords1.Y;
  408. int biggestX = points.Coords2.X;
  409. int biggestY = points.Coords2.Y;
  410. if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
  411. {
  412. return Int32Rect.Empty;
  413. }
  414. int width = biggestX - smallestX + 1;
  415. int height = biggestY - smallestY + 1;
  416. return new Int32Rect(smallestX, smallestY, width, height);
  417. }
  418. /// <summary>
  419. /// Changes size of bitmap to fit content.
  420. /// </summary>
  421. public void ClipCanvas()
  422. {
  423. var dimensions = GetContentDimensions();
  424. if (dimensions == Int32Rect.Empty) return;
  425. ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
  426. Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);
  427. }
  428. public void Reset()
  429. {
  430. if (IsReset)
  431. return;
  432. var dirtyRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
  433. LayerBitmap?.Dispose();
  434. LayerBitmap = new Surface(1, 1);
  435. Width = 1;
  436. Height = 1;
  437. Offset = new Thickness(0, 0, 0, 0);
  438. IsReset = true;
  439. LayerBitmapChanged?.Invoke(this, dirtyRect);
  440. }
  441. public void ClearCanvas()
  442. {
  443. if (IsReset)
  444. return;
  445. LayerBitmap.SkiaSurface.Canvas.Clear();
  446. InvokeLayerBitmapChange();
  447. }
  448. /// <summary>
  449. /// Converts layer WriteableBitmap to byte array.
  450. /// </summary>
  451. public byte[] ConvertBitmapToBytes()
  452. {
  453. return LayerBitmap.ToByteArray();
  454. }
  455. public SKRectI GetRect() => SKRectI.Create(OffsetX, OffsetY, Width, Height);
  456. public void CropIntersect(SKRectI rect)
  457. {
  458. SKRectI layerRect = GetRect();
  459. SKRectI intersect = SKRectI.Intersect(layerRect, rect);
  460. Crop(intersect);
  461. }
  462. public void Crop(SKRectI intersect)
  463. {
  464. if (intersect == SKRectI.Empty)
  465. {
  466. return;
  467. }
  468. using var oldSurface = LayerBitmap;
  469. int offsetX = (int)(Offset.Left - intersect.Left);
  470. int offsetY = (int)(Offset.Top - intersect.Top);
  471. Width = intersect.Width;
  472. Height = intersect.Height;
  473. LayerBitmap = LayerBitmap.Crop(offsetX, offsetY, Width, Height);
  474. Offset = new(intersect.Left, intersect.Top, 0, 0);
  475. }
  476. private Dictionary<Coordinates, SKColor> GetRelativePosition(Dictionary<Coordinates, SKColor> changedPixels)
  477. {
  478. return changedPixels.ToDictionary(
  479. d => new Coordinates(d.Key.X - OffsetX, d.Key.Y - OffsetY),
  480. d => d.Value);
  481. }
  482. private Tuple<DoubleCoords, bool> ExtractBorderData(BitmapPixelChanges pixels)
  483. {
  484. Coordinates firstCords = pixels.ChangedPixels.First().Key;
  485. int minX = firstCords.X;
  486. int minY = firstCords.Y;
  487. int maxX = minX;
  488. int maxY = minY;
  489. bool clipRequested = false;
  490. foreach (KeyValuePair<Coordinates, SKColor> pixel in pixels.ChangedPixels)
  491. {
  492. if (pixel.Key.X < minX)
  493. {
  494. minX = pixel.Key.X;
  495. }
  496. else if (pixel.Key.X > maxX)
  497. {
  498. maxX = pixel.Key.X;
  499. }
  500. if (pixel.Key.Y < minY)
  501. {
  502. minY = pixel.Key.Y;
  503. }
  504. else if (pixel.Key.Y > maxY)
  505. {
  506. maxY = pixel.Key.Y;
  507. }
  508. if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.Alpha == 0)
  509. {
  510. clipRequested = true;
  511. }
  512. }
  513. return new Tuple<DoubleCoords, bool>(
  514. new DoubleCoords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
  515. }
  516. private bool IsBorderPixel(Coordinates cords)
  517. {
  518. return cords.X - OffsetX == 0 || cords.Y - OffsetY == 0 || cords.X - OffsetX == Width - 1 ||
  519. cords.Y - OffsetY == Height - 1;
  520. }
  521. private bool OutOfBounds(Coordinates cords)
  522. {
  523. return cords.X < 0 || cords.X > Width - 1 || cords.Y < 0 || cords.Y > Height - 1;
  524. }
  525. private void ClipIfNecessary()
  526. {
  527. if (clipRequested)
  528. {
  529. ClipCanvas();
  530. clipRequested = false;
  531. }
  532. }
  533. private void IncreaseSizeToBottomAndRight(int newMaxX, int newMaxY)
  534. {
  535. if (MaxWidth - OffsetX < 0 || MaxHeight - OffsetY < 0)
  536. {
  537. return;
  538. }
  539. newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 1, MaxWidth - OffsetX);
  540. newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 1, MaxHeight - OffsetY);
  541. ResizeCanvas(0, 0, 0, 0, newMaxX, newMaxY);
  542. }
  543. private void IncreaseSizeToTopAndLeft(int newMinX, int newMinY)
  544. {
  545. newMinX = Math.Clamp(Math.Min(newMinX, Width), Math.Min(-OffsetX, OffsetX), 0);
  546. newMinY = Math.Clamp(Math.Min(newMinY, Height), Math.Min(-OffsetY, OffsetY), 0);
  547. Offset = new Thickness(
  548. Math.Clamp(OffsetX + newMinX, 0, MaxWidth),
  549. Math.Clamp(OffsetY + newMinY, 0, MaxHeight),
  550. 0,
  551. 0);
  552. int newWidth = Math.Clamp(Width - newMinX, 0, MaxWidth);
  553. int newHeight = Math.Clamp(Height - newMinY, 0, MaxHeight);
  554. int offsetX = Math.Abs(newWidth - Width);
  555. int offsetY = Math.Abs(newHeight - Height);
  556. ResizeCanvas(offsetX, offsetY, 0, 0, newWidth, newHeight);
  557. }
  558. private DoubleCoords GetEdgePoints()
  559. {
  560. Coordinates smallestPixel = CoordinatesCalculator.FindMinEdgeNonTransparentPixel(LayerBitmap);
  561. Coordinates biggestPixel = CoordinatesCalculator.FindMostEdgeNonTransparentPixel(LayerBitmap);
  562. return new DoubleCoords(smallestPixel, biggestPixel);
  563. }
  564. private void ResetOffset(BitmapPixelChanges pixels)
  565. {
  566. if (Width == 0 || Height == 0)
  567. {
  568. int offsetX = Math.Max(pixels.ChangedPixels.Min(x => x.Key.X), 0);
  569. int offsetY = Math.Max(pixels.ChangedPixels.Min(x => x.Key.Y), 0);
  570. Offset = new Thickness(offsetX, offsetY, 0, 0);
  571. }
  572. }
  573. /// <summary>
  574. /// Resizes canvas to new size with specified offset.
  575. /// </summary>
  576. private void ResizeCanvas(int offsetX, int offsetY, int offsetXSrc, int offsetYSrc, int newWidth, int newHeight)
  577. {
  578. Surface result = new Surface(newWidth, newHeight);
  579. LayerBitmap.SkiaSurface.Draw(result.SkiaSurface.Canvas, offsetX - offsetXSrc, offsetY - offsetYSrc, Surface.ReplacingPaint);
  580. LayerBitmap?.Dispose();
  581. LayerBitmap = result;
  582. Width = newWidth;
  583. Height = newHeight;
  584. }
  585. }
  586. }