RotateImage_Change.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. using ChunkyImageLib.Operations;
  2. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  3. using PixiEditor.ChangeableDocument.ChangeInfos.Root;
  4. using PixiEditor.ChangeableDocument.Enums;
  5. using Drawie.Backend.Core;
  6. using Drawie.Backend.Core.Numerics;
  7. using Drawie.Backend.Core.Surfaces.PaintImpl;
  8. using Drawie.Numerics;
  9. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  10. using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
  11. namespace PixiEditor.ChangeableDocument.Changes.Root;
  12. internal sealed class RotateImage_Change : Change
  13. {
  14. private readonly RotationAngle rotation;
  15. private List<Guid> membersToRotate;
  16. private VecI originalSize;
  17. private double originalHorAxisY;
  18. private double originalVerAxisX;
  19. private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
  20. private Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
  21. private int? frame;
  22. [GenerateMakeChangeAction]
  23. public RotateImage_Change(RotationAngle rotation, List<Guid>? membersToRotate, int frame)
  24. {
  25. this.rotation = rotation;
  26. membersToRotate ??= new List<Guid>();
  27. this.membersToRotate = membersToRotate;
  28. this.frame = frame < 0 ? null : frame;
  29. }
  30. public override bool InitializeAndValidate(Document target)
  31. {
  32. if (membersToRotate.Count > 0)
  33. {
  34. membersToRotate = target.ExtractLayers(membersToRotate);
  35. RectD? bounds = null;
  36. foreach (var layer in membersToRotate)
  37. {
  38. if (!target.HasMember(layer)) return false;
  39. if (frame != null)
  40. {
  41. var layerBounds = target.FindMember(layer).GetTightBounds(frame.Value);
  42. if (layerBounds.HasValue)
  43. {
  44. bounds = bounds?.Union(layerBounds.Value) ?? layerBounds.Value;
  45. }
  46. }
  47. }
  48. if(frame != null && (bounds == null || bounds.Value.IsZeroArea)) return false;
  49. }
  50. originalSize = target.Size;
  51. originalHorAxisY = target.HorizontalSymmetryAxisY;
  52. originalVerAxisX = target.VerticalSymmetryAxisX;
  53. return true;
  54. }
  55. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
  56. out bool ignoreInUndo)
  57. {
  58. var changes = Rotate(target);
  59. ignoreInUndo = false;
  60. return changes;
  61. }
  62. private void Resize(ChunkyImage img, Guid memberGuid,
  63. Dictionary<Guid, CommittedChunkStorage> deletedChunksDict, List<IChangeInfo>? changes)
  64. {
  65. RectI bounds = new RectI(VecI.Zero, img.CommittedSize);
  66. if (membersToRotate.Count > 0)
  67. {
  68. var preciseBounds = img.FindTightCommittedBounds();
  69. if (preciseBounds.HasValue)
  70. {
  71. bounds = preciseBounds.Value;
  72. }
  73. }
  74. if (bounds.IsZeroArea)
  75. {
  76. return;
  77. }
  78. int originalWidth = bounds.Width;
  79. int originalHeight = bounds.Height;
  80. int newWidth = rotation == RotationAngle.D180 ? originalWidth : originalHeight;
  81. int newHeight = rotation == RotationAngle.D180 ? originalHeight : originalWidth;
  82. VecI oldSize = new VecI(originalWidth, originalHeight);
  83. VecI newSize = new VecI(newWidth, newHeight);
  84. using Paint paint = new() { BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.Src };
  85. using Surface originalSurface = Surface.ForProcessing(oldSize, img.ProcessingColorSpace);
  86. img.DrawMostUpToDateRegionOn(
  87. bounds,
  88. ChunkResolution.Full,
  89. originalSurface.DrawingSurface,
  90. VecI.Zero);
  91. using Surface flipped = Surface.ForProcessing(newSize, img.ProcessingColorSpace);
  92. float translationX = newSize.X;
  93. float translationY = newSize.Y;
  94. switch (rotation)
  95. {
  96. case RotationAngle.D90:
  97. translationY = 0;
  98. break;
  99. case RotationAngle.D270:
  100. translationX = 0;
  101. break;
  102. }
  103. flipped.DrawingSurface.Canvas.Save();
  104. flipped.DrawingSurface.Canvas.Translate(translationX, translationY);
  105. flipped.DrawingSurface.Canvas.RotateRadians(RotationAngleToRadians(rotation), 0, 0);
  106. flipped.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);
  107. flipped.DrawingSurface.Canvas.Restore();
  108. if (membersToRotate.Count == 0)
  109. {
  110. img.EnqueueResize(newSize);
  111. }
  112. img.EnqueueClear();
  113. img.EnqueueDrawImage(bounds.Pos, flipped);
  114. var affArea = img.FindAffectedArea();
  115. deletedChunksDict.Add(memberGuid, new CommittedChunkStorage(img, affArea.Chunks));
  116. changes?.Add(new LayerImageArea_ChangeInfo(memberGuid, affArea));
  117. img.CommitChanges();
  118. }
  119. private OneOf<None, IChangeInfo, List<IChangeInfo>> Rotate(Document target)
  120. {
  121. if (membersToRotate.Count == 0)
  122. {
  123. return RotateWholeImage(target);
  124. }
  125. return RotateMembers(target, membersToRotate);
  126. }
  127. private OneOf<None, IChangeInfo, List<IChangeInfo>> RotateMembers(Document target, List<Guid> guids)
  128. {
  129. List<IChangeInfo> changes = new List<IChangeInfo>();
  130. target.ForEveryMember((member) =>
  131. {
  132. if (guids.Contains(member.Id))
  133. {
  134. if (member is ImageLayerNode layer)
  135. {
  136. if (frame != null)
  137. {
  138. Resize(layer.GetLayerImageAtFrame(frame.Value), layer.Id, deletedChunks, changes);
  139. }
  140. else
  141. {
  142. layer.ForEveryFrame(img =>
  143. {
  144. Resize(img, layer.Id, deletedChunks, changes);
  145. });
  146. }
  147. }
  148. else if (member is ITransformableObject transformableObject)
  149. {
  150. RectD? tightBounds = member.GetTightBounds(frame.Value);
  151. transformableObject.TransformationMatrix = transformableObject.TransformationMatrix.PostConcat(
  152. Matrix3X3.CreateRotation(
  153. RotationAngleToRadians(rotation),
  154. (float?)tightBounds?.Center.X ?? 0, (float?)tightBounds?.Center.Y ?? 0));
  155. }
  156. if (member.EmbeddedMask is null)
  157. return;
  158. Resize(member.EmbeddedMask, member.Id, deletedMaskChunks, null);
  159. }
  160. });
  161. return changes;
  162. }
  163. private OneOf<None, IChangeInfo, List<IChangeInfo>> RotateWholeImage(Document target)
  164. {
  165. int newWidth = rotation == RotationAngle.D180 ? target.Size.X : target.Size.Y;
  166. int newHeight = rotation == RotationAngle.D180 ? target.Size.Y : target.Size.X;
  167. VecI newSize = new VecI(newWidth, newHeight);
  168. double normalizedSymmX = originalVerAxisX / Math.Max(target.Size.X, 0.1f);
  169. double normalizedSymmY = originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
  170. target.Size = newSize;
  171. target.VerticalSymmetryAxisX = Math.Round(newSize.X * normalizedSymmX * 2) / 2;
  172. target.HorizontalSymmetryAxisY = Math.Round(newSize.Y * normalizedSymmY * 2) / 2;
  173. target.ForEveryMember((member) =>
  174. {
  175. if (member is ImageLayerNode layer)
  176. {
  177. if (frame != null)
  178. {
  179. Resize(layer.GetLayerImageAtFrame(frame.Value), layer.Id, deletedChunks, null);
  180. }
  181. else
  182. {
  183. layer.ForEveryFrame(img =>
  184. {
  185. Resize(img, layer.Id, deletedChunks, null);
  186. });
  187. }
  188. }
  189. if (member.EmbeddedMask is null)
  190. return;
  191. Resize(member.EmbeddedMask, member.Id, deletedMaskChunks, null);
  192. });
  193. return new Size_ChangeInfo(newSize, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
  194. }
  195. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
  196. {
  197. if (membersToRotate.Count == 0)
  198. {
  199. return RevertRotateWholeImage(target);
  200. }
  201. return RevertRotateMembers(target);
  202. }
  203. private OneOf<None, IChangeInfo, List<IChangeInfo>> RevertRotateWholeImage(Document target)
  204. {
  205. target.Size = originalSize;
  206. RevertRotateMembers(target);
  207. target.HorizontalSymmetryAxisY = originalHorAxisY;
  208. target.VerticalSymmetryAxisX = originalVerAxisX;
  209. return new Size_ChangeInfo(originalSize, originalVerAxisX, originalHorAxisY);
  210. }
  211. private List<IChangeInfo> RevertRotateMembers(Document target)
  212. {
  213. List<IChangeInfo> revertChanges = new List<IChangeInfo>();
  214. target.ForEveryMember((member) =>
  215. {
  216. if (membersToRotate.Count > 0 && !membersToRotate.Contains(member.Id)) return;
  217. if (member is ImageLayerNode layer)
  218. {
  219. if (frame != null)
  220. {
  221. var layerImage = layer.GetLayerImageAtFrame(frame.Value);
  222. layerImage.EnqueueResize(originalSize);
  223. deletedChunks[layer.Id].ApplyChunksToImage(layerImage);
  224. revertChanges.Add(new LayerImageArea_ChangeInfo(layer.Id, layerImage.FindAffectedArea()));
  225. layerImage.CommitChanges();
  226. }
  227. else
  228. {
  229. layer.ForEveryFrame(img =>
  230. {
  231. img.EnqueueResize(originalSize);
  232. deletedChunks[layer.Id].ApplyChunksToImage(img);
  233. revertChanges.Add(new LayerImageArea_ChangeInfo(layer.Id, img.FindAffectedArea()));
  234. img.CommitChanges();
  235. });
  236. }
  237. }
  238. if (member.EmbeddedMask is null)
  239. return;
  240. member.EmbeddedMask.EnqueueResize(originalSize);
  241. deletedMaskChunks[member.Id].ApplyChunksToImage(member.EmbeddedMask);
  242. revertChanges.Add(new LayerImageArea_ChangeInfo(member.Id, member.EmbeddedMask.FindAffectedArea()));
  243. member.EmbeddedMask.CommitChanges();
  244. });
  245. DisposeDeletedChunks();
  246. return revertChanges;
  247. }
  248. private void DisposeDeletedChunks()
  249. {
  250. foreach (var stored in deletedChunks)
  251. stored.Value.Dispose();
  252. deletedChunks = new();
  253. foreach (var stored in deletedMaskChunks)
  254. stored.Value.Dispose();
  255. deletedMaskChunks = new();
  256. }
  257. public override void Dispose()
  258. {
  259. DisposeDeletedChunks();
  260. }
  261. private float RotationAngleToRadians(RotationAngle rotationAngle)
  262. {
  263. return rotationAngle switch
  264. {
  265. RotationAngle.D90 => 90f * Matrix3X3.DegreesToRadians,
  266. RotationAngle.D180 => 180f * Matrix3X3.DegreesToRadians,
  267. RotationAngle.D270 => 270f * Matrix3X3.DegreesToRadians,
  268. _ => throw new ArgumentOutOfRangeException(nameof(rotationAngle), rotationAngle, null)
  269. };
  270. }
  271. }