Surface.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using System.Runtime.CompilerServices;
  2. using System.Runtime.InteropServices;
  3. using PixiEditor.DrawingApi.Core.ColorsImpl;
  4. using PixiEditor.DrawingApi.Core.Numerics;
  5. using PixiEditor.DrawingApi.Core.Surface;
  6. using PixiEditor.DrawingApi.Core.Surface.ImageData;
  7. using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
  8. namespace ChunkyImageLib;
  9. public class Surface : IDisposable
  10. {
  11. private bool disposed;
  12. public IntPtr PixelBuffer { get; }
  13. public DrawingSurface DrawingSurface { get; }
  14. public int BytesPerPixel { get; }
  15. public VecI Size { get; }
  16. private Paint drawingPaint = new Paint() { BlendMode = BlendMode.Src };
  17. private Paint nearestNeighborReplacingPaint = new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None };
  18. public Surface(VecI size)
  19. {
  20. if (size.X < 1 || size.Y < 1)
  21. throw new ArgumentException("Width and height must be >=1");
  22. Size = size;
  23. BytesPerPixel = 8;
  24. PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
  25. DrawingSurface = CreateDrawingSurface();
  26. }
  27. public Surface(Surface original) : this(original.Size)
  28. {
  29. DrawingSurface.Canvas.DrawSurface(original.DrawingSurface, 0, 0);
  30. }
  31. public static Surface Combine(int width, int height, List<(Image img, VecI offset)> images)
  32. {
  33. Surface surface = new Surface(new VecI(width, height));
  34. foreach (var (img, offset) in images)
  35. {
  36. surface.DrawingSurface.Canvas.DrawImage(img, offset.X, offset.Y);
  37. }
  38. return surface;
  39. }
  40. public static Surface Load(string path)
  41. {
  42. if (!File.Exists(path))
  43. throw new FileNotFoundException(null, path);
  44. using var image = Image.FromEncodedData(path);
  45. if (image is null)
  46. throw new ArgumentException($"The image with path {path} couldn't be loaded");
  47. var surface = new Surface(new VecI(image.Width, image.Height));
  48. surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);
  49. return surface;
  50. }
  51. public static Surface Load(byte[] encoded)
  52. {
  53. using var image = Image.FromEncodedData(encoded);
  54. var surface = new Surface(new VecI(image.Width, image.Height));
  55. surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);
  56. return surface;
  57. }
  58. public unsafe void DrawBytes(VecI size, byte[] bytes, ColorType colorType, AlphaType alphaType)
  59. {
  60. ImageInfo info = new ImageInfo(size.X, size.Y, colorType, alphaType);
  61. fixed (void* pointer = bytes)
  62. {
  63. using Pixmap map = new(info, new IntPtr(pointer));
  64. using DrawingSurface surface = DrawingSurface.Create(map);
  65. surface.Draw(DrawingSurface.Canvas, 0, 0, drawingPaint);
  66. }
  67. }
  68. public Surface ResizeNearestNeighbor(VecI newSize)
  69. {
  70. using Image image = DrawingSurface.Snapshot();
  71. Surface newSurface = new(newSize);
  72. newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
  73. return newSurface;
  74. }
  75. public unsafe void CopyTo(Surface other)
  76. {
  77. if (other.Size != Size)
  78. throw new ArgumentException("Target Surface must have the same dimensions");
  79. int bytesC = Size.X * Size.Y * BytesPerPixel;
  80. using var pixmap = other.DrawingSurface.PeekPixels();
  81. Buffer.MemoryCopy((void*)PixelBuffer, (void*)pixmap.GetPixels(), bytesC, bytesC);
  82. }
  83. /// <summary>
  84. /// Consider getting a pixmap from SkiaSurface.PeekPixels().GetPixels() and writing into it's buffer for bulk pixel get/set. Don't forget to dispose the pixmap afterwards.
  85. /// </summary>
  86. public unsafe Color GetSRGBPixel(VecI pos)
  87. {
  88. Half* ptr = (Half*)(PixelBuffer + (pos.X + pos.Y * Size.X) * BytesPerPixel);
  89. float a = (float)ptr[3];
  90. return (Color)new ColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
  91. }
  92. public void SetSRGBPixel(VecI pos, Color color)
  93. {
  94. drawingPaint.Color = color;
  95. DrawingSurface.Canvas.DrawPixel(pos.X, pos.Y, drawingPaint);
  96. }
  97. public unsafe bool IsFullyTransparent()
  98. {
  99. ulong* ptr = (ulong*)PixelBuffer;
  100. for (int i = 0; i < Size.X * Size.Y; i++)
  101. {
  102. // ptr[i] actually contains 4 16-bit floats. We only care about the first one which is alpha.
  103. // An empty pixel can have alpha of 0 or -0 (not sure if -0 actually ever comes up). 0 in hex is 0x0, -0 in hex is 0x8000
  104. if ((ptr[i] & 0x1111_0000_0000_0000) != 0 && (ptr[i] & 0x1111_0000_0000_0000) != 0x8000_0000_0000_0000)
  105. return false;
  106. }
  107. return true;
  108. }
  109. public void SaveToDesktop(string filename = "savedSurface.png")
  110. {
  111. using var final = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb()));
  112. final.Canvas.DrawSurface(DrawingSurface, 0, 0);
  113. using (var snapshot = final.Snapshot())
  114. {
  115. using var stream = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename));
  116. using var png = snapshot.Encode();
  117. png.SaveTo(stream);
  118. }
  119. }
  120. private DrawingSurface CreateDrawingSurface()
  121. {
  122. var surface = PixiEditor.DrawingApi.Core.Surface.DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
  123. if (surface is null)
  124. throw new InvalidOperationException($"Could not create surface (Size:{Size})");
  125. return surface;
  126. }
  127. private static unsafe IntPtr CreateBuffer(int width, int height, int bytesPerPixel)
  128. {
  129. int byteC = width * height * bytesPerPixel;
  130. var buffer = Marshal.AllocHGlobal(byteC);
  131. Unsafe.InitBlockUnaligned((byte*)buffer, 0, (uint)byteC);
  132. return buffer;
  133. }
  134. public void Dispose()
  135. {
  136. if (disposed)
  137. return;
  138. disposed = true;
  139. drawingPaint.Dispose();
  140. nearestNeighborReplacingPaint.Dispose();
  141. Marshal.FreeHGlobal(PixelBuffer);
  142. GC.SuppressFinalize(this);
  143. }
  144. ~Surface()
  145. {
  146. Marshal.FreeHGlobal(PixelBuffer);
  147. }
  148. }