ResizeImage_Change.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  2. using PixiEditor.ChangeableDocument.ChangeInfos.Root;
  3. using PixiEditor.ChangeableDocument.Enums;
  4. using Drawie.Backend.Core;
  5. using Drawie.Backend.Core.Numerics;
  6. using Drawie.Backend.Core.Surfaces;
  7. using Drawie.Backend.Core.Surfaces.PaintImpl;
  8. using Drawie.Numerics;
  9. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  10. using BlendMode = Drawie.Backend.Core.Surfaces.BlendMode;
  11. namespace PixiEditor.ChangeableDocument.Changes.Root;
  12. internal class ResizeImage_Change : Change
  13. {
  14. private readonly VecI newSize;
  15. private readonly ResamplingMethod method;
  16. private VecI originalSize;
  17. private double originalHorAxisY;
  18. private double originalVerAxisX;
  19. private Dictionary<Guid, CommittedChunkStorage> savedChunks = new();
  20. private Dictionary<Guid, CommittedChunkStorage> savedMaskChunks = new();
  21. [GenerateMakeChangeAction]
  22. public ResizeImage_Change(VecI size, ResamplingMethod method)
  23. {
  24. this.newSize = size;
  25. this.method = method;
  26. }
  27. public override bool InitializeAndValidate(Document target)
  28. {
  29. if (newSize.X < 1 || newSize.Y < 1)
  30. return false;
  31. originalSize = target.Size;
  32. originalHorAxisY = target.HorizontalSymmetryAxisY;
  33. originalVerAxisX = target.VerticalSymmetryAxisX;
  34. return true;
  35. }
  36. private static FilterQuality ToFilterQuality(ResamplingMethod method, bool downscaling) =>
  37. (method, downscaling) switch
  38. {
  39. (ResamplingMethod.NearestNeighbor, _) => FilterQuality.None,
  40. (ResamplingMethod.Bilinear, true) => FilterQuality.Medium,
  41. (ResamplingMethod.Bilinear, false) => FilterQuality.Low,
  42. (ResamplingMethod.Bicubic, _) => FilterQuality.High,
  43. _ => throw new ArgumentOutOfRangeException(),
  44. };
  45. private void ScaleChunkyImage(ChunkyImage image)
  46. {
  47. using Surface originalSurface = Surface.ForProcessing(originalSize, image.ProcessingColorSpace);
  48. image.DrawMostUpToDateRegionOn(
  49. new(VecI.Zero, originalSize),
  50. ChunkResolution.Full,
  51. originalSurface.DrawingSurface,
  52. VecI.Zero);
  53. bool downscaling = newSize.LengthSquared < originalSize.LengthSquared;
  54. FilterQuality quality = ToFilterQuality(method, downscaling);
  55. using Paint paint = new() { FilterQuality = quality, BlendMode = BlendMode.Src, };
  56. using Surface newSurface = Surface.ForProcessing(newSize, image.ProcessingColorSpace);
  57. newSurface.DrawingSurface.Canvas.Save();
  58. newSurface.DrawingSurface.Canvas.Scale(newSize.X / (float)originalSize.X, newSize.Y / (float)originalSize.Y);
  59. newSurface.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);
  60. newSurface.DrawingSurface.Canvas.Restore();
  61. image.EnqueueResize(newSize);
  62. image.EnqueueClear();
  63. image.EnqueueDrawImage(VecI.Zero, newSurface);
  64. }
  65. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
  66. out bool ignoreInUndo)
  67. {
  68. if (originalSize == newSize)
  69. {
  70. ignoreInUndo = true;
  71. return new None();
  72. }
  73. target.Size = newSize;
  74. target.VerticalSymmetryAxisX = Math.Clamp(originalVerAxisX, 0, target.Size.X);
  75. target.HorizontalSymmetryAxisY = Math.Clamp(originalHorAxisY, 0, target.Size.Y);
  76. target.ForEveryMember(member =>
  77. {
  78. if (member is ImageLayerNode layer)
  79. {
  80. layer.ForEveryFrame((img, id) =>
  81. {
  82. ScaleChunkyImage(img);
  83. var affected = img.FindAffectedArea();
  84. savedChunks[id] = new CommittedChunkStorage(img, affected.Chunks);
  85. img.CommitChanges();
  86. });
  87. }
  88. else if (member is IScalable scalableLayer)
  89. {
  90. VecD multiplier = new VecD(newSize.X / (double)originalSize.X, newSize.Y / (double)originalSize.Y);
  91. scalableLayer.Resize(multiplier);
  92. }
  93. // Add support for different Layer types
  94. if (member.EmbeddedMask is not null)
  95. {
  96. ScaleChunkyImage(member.EmbeddedMask);
  97. var affected = member.EmbeddedMask.FindAffectedArea();
  98. savedMaskChunks[member.Id] = new CommittedChunkStorage(member.EmbeddedMask, affected.Chunks);
  99. member.EmbeddedMask.CommitChanges();
  100. }
  101. });
  102. ignoreInUndo = false;
  103. return new Size_ChangeInfo(newSize, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
  104. }
  105. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
  106. {
  107. target.Size = originalSize;
  108. target.ForEveryMember((member) =>
  109. {
  110. if (member is ImageLayerNode layer)
  111. {
  112. layer.ForEveryFrame((layerImage, id) =>
  113. {
  114. layerImage.EnqueueResize(originalSize);
  115. layerImage.EnqueueClear();
  116. savedChunks[id].ApplyChunksToImage(layerImage);
  117. layerImage.CommitChanges();
  118. });
  119. }
  120. else if (member is IScalable scalableLayer)
  121. {
  122. VecD multiplier = new VecD(originalSize.X / (double)newSize.X, originalSize.Y / (double)newSize.Y);
  123. scalableLayer.Resize(multiplier);
  124. }
  125. if (member.EmbeddedMask is not null)
  126. {
  127. member.EmbeddedMask.EnqueueResize(originalSize);
  128. member.EmbeddedMask.EnqueueClear();
  129. savedMaskChunks[member.Id].ApplyChunksToImage(member.EmbeddedMask);
  130. member.EmbeddedMask.CommitChanges();
  131. }
  132. });
  133. target.HorizontalSymmetryAxisY = originalHorAxisY;
  134. target.VerticalSymmetryAxisX = originalVerAxisX;
  135. foreach (var stored in savedChunks)
  136. stored.Value.Dispose();
  137. savedChunks = new();
  138. foreach (var stored in savedMaskChunks)
  139. stored.Value.Dispose();
  140. savedMaskChunks = new();
  141. return new Size_ChangeInfo(originalSize, originalVerAxisX, originalHorAxisY);
  142. }
  143. public override void Dispose()
  144. {
  145. foreach (var layer in savedChunks)
  146. layer.Value.Dispose();
  147. foreach (var mask in savedMaskChunks)
  148. mask.Value.Dispose();
  149. }
  150. }