StorageBasedChange.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. using PixiEditor.Models.DataHolders;
  2. using PixiEditor.Models.IO;
  3. using PixiEditor.Models.Layers;
  4. using SkiaSharp;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Windows;
  11. namespace PixiEditor.Models.Undo
  12. {
  13. /// <summary>
  14. /// A class that allows to save layers on disk and load them on Undo/Redo.
  15. /// </summary>
  16. public class StorageBasedChange : IDisposable
  17. {
  18. public static string DefaultUndoChangeLocation { get; } = Path.Join(Path.GetTempPath(), "PixiEditor", Guid.NewGuid().ToString(), "UndoStack");
  19. public string UndoChangeLocation { get; set; }
  20. public UndoLayer[] StoredLayers { get; set; }
  21. private List<Guid> layersToStore = new List<Guid>();
  22. public Document Document { get; }
  23. public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool saveOnStartup = true)
  24. {
  25. Document = doc;
  26. Initialize(layers, DefaultUndoChangeLocation, saveOnStartup);
  27. }
  28. public StorageBasedChange(Document doc, IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup = true)
  29. {
  30. Document = doc;
  31. Initialize(layers, undoChangeLocation, saveOnStartup);
  32. }
  33. public StorageBasedChange(Document doc, IEnumerable<LayerChunk> chunks, bool saveOnStartup = true)
  34. {
  35. Document = doc;
  36. var chunkData = chunks as LayerChunk[] ?? chunks.ToArray();
  37. LayerChunk[] layerChunks = new LayerChunk[chunkData.Length];
  38. for (var i = 0; i < chunkData.Length; i++)
  39. {
  40. var chunk = chunkData[i];
  41. layerChunks[i] = chunk;
  42. layersToStore.Add(chunk.Layer.GuidValue);
  43. }
  44. UndoChangeLocation = DefaultUndoChangeLocation;
  45. GenerateUndoLayers(layerChunks);
  46. if (saveOnStartup)
  47. {
  48. SaveLayersOnDevice();
  49. }
  50. }
  51. private void Initialize(IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup)
  52. {
  53. var layersArray = layers as Layer[] ?? layers.ToArray();
  54. LayerChunk[] layerChunks = new LayerChunk[layersArray.Length];
  55. for (var i = 0; i < layersArray.Length; i++)
  56. {
  57. var layer = layersArray[i];
  58. layerChunks[i] = new LayerChunk(layer, SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height));
  59. layersToStore.Add(layer.GuidValue);
  60. }
  61. UndoChangeLocation = undoChangeLocation;
  62. GenerateUndoLayers(layerChunks);
  63. if (saveOnStartup)
  64. {
  65. SaveLayersOnDevice();
  66. }
  67. }
  68. public void SaveLayersOnDevice()
  69. {
  70. int i = 0;
  71. foreach (var layerGuid in layersToStore)
  72. {
  73. Layer layer = Document.Layers.First(x => x.GuidValue == layerGuid);
  74. UndoLayer storedLayer = StoredLayers[i];
  75. if (Directory.Exists(Path.GetDirectoryName(storedLayer.StoredPngLayerName)))
  76. {
  77. // Calculate absolute rect to relative rect
  78. SKRectI finalRect = SKRectI.Create(
  79. storedLayer.SerializedRect.Left - layer.OffsetX,
  80. storedLayer.SerializedRect.Top - layer.OffsetY,
  81. storedLayer.SerializedRect.Width,
  82. storedLayer.SerializedRect.Height);
  83. using var image = layer.LayerBitmap.SkiaSurface.Snapshot();
  84. Surface targetSizeSurface = new Surface(finalRect.Width, finalRect.Height);
  85. targetSizeSurface.SkiaSurface.Canvas.DrawImage(image, finalRect, SKRect.Create(0, 0, finalRect.Width, finalRect.Height), Surface.ReplacingPaint);
  86. Exporter.SaveAsGZippedBytes(storedLayer.StoredPngLayerName, targetSizeSurface);
  87. }
  88. i++;
  89. }
  90. layersToStore = new List<Guid>();
  91. }
  92. /// <summary>
  93. /// Loads saved layers from disk.
  94. /// </summary>
  95. /// <returns>Array of saved layers.</returns>
  96. public Layer[] LoadLayersFromDevice()
  97. {
  98. Layer[] layers = new Layer[StoredLayers.Length];
  99. for (int i = 0; i < StoredLayers.Length; i++)
  100. {
  101. UndoLayer storedLayer = StoredLayers[i];
  102. var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
  103. layers[i] = new Layer(storedLayer.Name, bitmap)
  104. {
  105. Width = storedLayer.Width,
  106. Height = storedLayer.Height,
  107. Offset = new Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),
  108. Opacity = storedLayer.Opacity,
  109. MaxWidth = storedLayer.MaxWidth,
  110. MaxHeight = storedLayer.MaxHeight,
  111. IsVisible = storedLayer.IsVisible,
  112. IsActive = storedLayer.IsActive,
  113. LayerHighlightColor = storedLayer.LayerHighlightColor
  114. };
  115. layers[i].ChangeGuid(storedLayer.LayerGuid);
  116. File.Delete(StoredLayers[i].StoredPngLayerName);
  117. }
  118. layersToStore = layers.Select(x => x.GuidValue).ToList();
  119. return layers;
  120. }
  121. /// <summary>
  122. /// Creates UndoManager ready Change instance, where undo process loads layers from device, and redo saves them.
  123. /// </summary>
  124. /// <param name="undoProcess">Method that is invoked on undo, with loaded layers parameter and UndoLayer array data.</param>
  125. /// <param name="processArgs">Custom parameters for undo process.</param>
  126. /// <param name="redoProcess">Method that is invoked on redo with custom object array parameters.</param>
  127. /// <param name="redoProcessParameters">Parameters for redo process.</param>
  128. /// <param name="description">Undo change description.</param>
  129. /// <returns>UndoManager ready Change instance.</returns>
  130. public Change ToChange(Action<Layer[], UndoLayer[], object[]> undoProcess, object[] processArgs, Action<object[]> redoProcess, object[] redoProcessParameters, string description = "")
  131. {
  132. Action<object[]> finalUndoProcess = processParameters =>
  133. {
  134. Layer[] layers = LoadLayersFromDevice();
  135. undoProcess(layers, StoredLayers, processParameters);
  136. };
  137. Action<object[]> finalRedoProcess = parameters =>
  138. {
  139. SaveLayersOnDevice();
  140. redoProcess(parameters);
  141. };
  142. var change = new Change(finalUndoProcess, processArgs, finalRedoProcess, redoProcessParameters, description);
  143. change.DisposeProcess = (_, _) => Dispose();
  144. return change;
  145. }
  146. /// <summary>
  147. /// Creates UndoManager ready Change instance, where undo and redo is the same, before process images are loaded from disk and current ones are saved.
  148. /// </summary>
  149. /// <param name="undoRedoProcess">Process that is invoked on redo and undo.</param>
  150. /// <param name="processArgs">Custom parameters for undo and redo process.</param>
  151. /// <param name="description">Undo change description.</param>
  152. /// <returns>UndoManager ready 'Change' instance.</returns>
  153. public Change ToChange(Action<Layer[], UndoLayer[], object[]> undoRedoProcess, object[] processArgs, string description = "")
  154. {
  155. Action<object[]> finalProcess = processParameters =>
  156. {
  157. Layer[] layers = LoadLayersFromDevice();
  158. LayerChunk[] chunks = new LayerChunk[layers.Length];
  159. for (int i = 0; i < layers.Length; i++)
  160. {
  161. chunks[i] = new LayerChunk(layers[i], StoredLayers[i].SerializedRect);
  162. }
  163. GenerateUndoLayers(chunks);
  164. SaveLayersOnDevice();
  165. undoRedoProcess(layers, StoredLayers, processParameters);
  166. };
  167. var change = new Change(finalProcess, processArgs, finalProcess, processArgs, description);
  168. change.DisposeProcess = (_, _) => Dispose();
  169. return change;
  170. }
  171. /// <summary>
  172. /// Creates UndoManager ready Change instance, where undo process loads layers from device, and redo saves them.
  173. /// </summary>
  174. /// <param name="undoProcess">Method that is invoked on undo, with loaded layers parameter and UndoLayer array data.</param>
  175. /// <param name="redoProcess">Method that is invoked on redo with custom object array parameters.</param>
  176. /// <param name="redoProcessParameters">Parameters for redo process.</param>
  177. /// <param name="description">Undo change description.</param>
  178. /// <returns>UndoManager ready Change instance.</returns>
  179. public Change ToChange(Action<Layer[], UndoLayer[]> undoProcess, Action<object[]> redoProcess, object[] redoProcessParameters, string description = "")
  180. {
  181. Action<object[]> finalUndoProcess = _ =>
  182. {
  183. Layer[] layers = LoadLayersFromDevice();
  184. undoProcess(layers, StoredLayers);
  185. };
  186. Action<object[]> finalRedoProcess = parameters =>
  187. {
  188. SaveLayersOnDevice();
  189. redoProcess(parameters);
  190. };
  191. var change = new Change(finalUndoProcess, null, finalRedoProcess, redoProcessParameters, description);
  192. change.DisposeProcess = (_, _) => Dispose();
  193. return change;
  194. }
  195. /// <summary>
  196. /// Creates UndoManager ready Change instance, where undo process saves layers on device, and redo loads them.
  197. /// </summary>
  198. /// <param name="undoProcess">Method that is invoked on undo, with loaded layers parameter and UndoLayer array data.</param>
  199. /// <param name="undoProcessParameters">Parameters for undo process.</param>
  200. /// <param name="redoProcess">Method that is invoked on redo with custom object array parameters.</param>
  201. /// <param name="description">Undo change description.</param>
  202. /// <returns>UndoManager ready Change instance.</returns>
  203. public Change ToChange(Action<object[]> undoProcess, object[] undoProcessParameters, Action<Layer[], UndoLayer[]> redoProcess, string description = "")
  204. {
  205. Action<object[]> finalUndoProcess = parameters =>
  206. {
  207. SaveLayersOnDevice();
  208. undoProcess(parameters);
  209. };
  210. Action<object[]> finalRedoProcess = parameters =>
  211. {
  212. Layer[] layers = LoadLayersFromDevice();
  213. redoProcess(layers, StoredLayers);
  214. };
  215. var change = new Change(finalUndoProcess, undoProcessParameters, finalRedoProcess, null, description);
  216. change.DisposeProcess = (_, _) => Dispose();
  217. return change;
  218. }
  219. /// <summary>
  220. /// Creates UndoManager ready Change instance, where undo process saves layers on device, and redo loads them.
  221. /// </summary>
  222. /// <param name="undoProcess">Method that is invoked on undo, with loaded layers parameter and UndoLayer array data.</param>
  223. /// <param name="undoProcessParameters">Parameters for undo process.</param>
  224. /// <param name="redoProcess">Method that is invoked on redo with custom object array parameters.</param>
  225. /// <param name="redoProcessArgs">Parameters for redo process.</param>
  226. /// <param name="description">Undo change description.</param>
  227. /// <returns>UndoManager ready Change instance.</returns>
  228. public Change ToChange(Action<object[]> undoProcess, object[] undoProcessParameters, Action<Layer[], UndoLayer[], object[]> redoProcess, object[] redoProcessArgs, string description = "")
  229. {
  230. Action<object[]> finalUndoProcess = parameters =>
  231. {
  232. SaveLayersOnDevice();
  233. undoProcess(parameters);
  234. };
  235. Action<object[]> finalRedoProcess = parameters =>
  236. {
  237. Layer[] layers = LoadLayersFromDevice();
  238. redoProcess(layers, StoredLayers, parameters);
  239. };
  240. var change = new Change(finalUndoProcess, undoProcessParameters, finalRedoProcess, redoProcessArgs, description);
  241. change.DisposeProcess = (_, _) => Dispose();
  242. return change;
  243. }
  244. /// <summary>
  245. /// Generates UndoLayer[] StoredLayers data.
  246. /// </summary>
  247. private void GenerateUndoLayers(LayerChunk[] chunks)
  248. {
  249. StoredLayers = new UndoLayer[layersToStore.Count];
  250. int i = 0;
  251. foreach (var layerGuid in layersToStore)
  252. {
  253. Layer layer = Document.Layers.First(x => x.GuidValue == layerGuid);
  254. if (!Document.Layers.Contains(layer))
  255. {
  256. throw new ArgumentException("Provided document doesn't contain selected layer");
  257. }
  258. int index = Document.Layers.IndexOf(layer);
  259. string fileName = layer.Name + Guid.NewGuid();
  260. StoredLayers[i] = new UndoLayer(
  261. Path.Join(
  262. UndoChangeLocation,
  263. Convert.ToBase64String(Encoding.UTF8.GetBytes(fileName)) + ".undoimg"),
  264. layer,
  265. index,
  266. chunks[i].AbsoluteChunkRect);
  267. i++;
  268. }
  269. }
  270. public static void BasicUndoProcess(Layer[] layers, UndoLayer[] data, object[] args)
  271. {
  272. if (args.Length > 0 && args[0] is Document document)
  273. {
  274. for (int i = 0; i < layers.Length; i++)
  275. {
  276. Layer layer = layers[i];
  277. UndoLayer layerData = data[i];
  278. var foundLayer = document.Layers.FirstOrDefault(x => x.GuidValue == layerData.LayerGuid);
  279. if (foundLayer != null)
  280. {
  281. ApplyChunkToLayer(foundLayer, layerData, layer.LayerBitmap);
  282. }
  283. else
  284. {
  285. document.RemoveLayer(layerData.LayerIndex, false);
  286. document.Layers.Insert(layerData.LayerIndex, layer);
  287. }
  288. if (layerData.IsActive)
  289. {
  290. document.SetMainActiveLayer(layerData.LayerIndex);
  291. }
  292. }
  293. }
  294. }
  295. private static void ApplyChunkToLayer(Layer layer, UndoLayer layerData, Surface chunk)
  296. {
  297. bool widthBigger = layer.Width < chunk.Width;
  298. bool heightBigger = layer.Height < chunk.Height;
  299. int targetWidth = widthBigger ? chunk.Width : layer.Width;
  300. int targetHeight = heightBigger ? chunk.Height : layer.Height;
  301. targetWidth = Math.Clamp(targetWidth, 0, layerData.MaxWidth);
  302. targetHeight = Math.Clamp(targetHeight, 0, layerData.MaxHeight);
  303. int offsetDiffX = layerData.OffsetX - layer.OffsetX;
  304. int offsetDiffY = layerData.OffsetY - layer.OffsetY;
  305. bool offsetXBigger = Math.Abs(offsetDiffX) > 0;
  306. bool offsetYBigger = Math.Abs(offsetDiffY) > 0;
  307. int targetOffsetX = offsetXBigger ? layerData.SerializedRect.Left : layerData.OffsetX;
  308. int targetOffsetY = offsetYBigger ? layerData.SerializedRect.Top : layerData.OffsetY;
  309. targetOffsetX = Math.Max(0, targetOffsetX);
  310. targetOffsetY = Math.Max(0, targetOffsetY);
  311. Surface targetSizeSurface = new Surface(targetWidth, targetHeight);
  312. using var foundLayerSnapshot = layer.LayerBitmap.SkiaSurface.Snapshot();
  313. targetSizeSurface.SkiaSurface.Canvas.DrawImage(
  314. foundLayerSnapshot,
  315. SKRect.Create(offsetDiffX, offsetDiffY, layer.Width, layer.Height),
  316. SKRect.Create(0, 0, targetWidth, targetHeight),
  317. Surface.ReplacingPaint);
  318. layer.Offset = new Thickness(targetOffsetX, targetOffsetY, 0, 0);
  319. SKRect finalRect = SKRect.Create(
  320. layerData.SerializedRect.Left - layer.OffsetX,
  321. layerData.SerializedRect.Top - layer.OffsetY,
  322. layerData.SerializedRect.Width,
  323. layerData.SerializedRect.Height);
  324. using var snapshot = chunk.SkiaSurface.Snapshot();
  325. targetSizeSurface.SkiaSurface.Canvas.DrawImage(
  326. snapshot,
  327. finalRect,
  328. Surface.ReplacingPaint);
  329. layer.LayerBitmap = targetSizeSurface;
  330. }
  331. public void Dispose()
  332. {
  333. for (int i = 0; i < StoredLayers.Length; i++)
  334. {
  335. if (File.Exists(StoredLayers[i].StoredPngLayerName))
  336. File.Delete(StoredLayers[i].StoredPngLayerName);
  337. }
  338. }
  339. }
  340. }