Surface.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. if (size.X > 10000 || size.Y > 10000)
  23. throw new ArgumentException("Width and height must be <=10000");
  24. Size = size;
  25. BytesPerPixel = 8;
  26. PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
  27. DrawingSurface = CreateDrawingSurface();
  28. }
  29. public Surface(Surface original) : this(original.Size)
  30. {
  31. DrawingSurface.Canvas.DrawSurface(original.DrawingSurface, 0, 0);
  32. }
  33. public static Surface Load(string path)
  34. {
  35. if (!File.Exists(path))
  36. throw new FileNotFoundException(null, path);
  37. using var image = Image.FromEncodedData(path);
  38. if (image is null)
  39. throw new ArgumentException($"The image with path {path} couldn't be loaded");
  40. var surface = new Surface(new VecI(image.Width, image.Height));
  41. surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);
  42. return surface;
  43. }
  44. public unsafe void DrawBytes(VecI size, byte[] bytes, ColorType colorType, AlphaType alphaType)
  45. {
  46. ImageInfo info = new ImageInfo(size.X, size.Y, colorType, alphaType);
  47. fixed (void* pointer = bytes)
  48. {
  49. using Pixmap map = new(info, new IntPtr(pointer));
  50. using DrawingSurface surface = DrawingSurface.Create(map);
  51. surface.Draw(DrawingSurface.Canvas, 0, 0, drawingPaint);
  52. }
  53. }
  54. public Surface ResizeNearestNeighbor(VecI newSize)
  55. {
  56. using Image image = DrawingSurface.Snapshot();
  57. Surface newSurface = new(newSize);
  58. newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
  59. return newSurface;
  60. }
  61. public unsafe void CopyTo(Surface other)
  62. {
  63. if (other.Size != Size)
  64. throw new ArgumentException("Target Surface must have the same dimensions");
  65. int bytesC = Size.X * Size.Y * BytesPerPixel;
  66. using var pixmap = other.DrawingSurface.PeekPixels();
  67. Buffer.MemoryCopy((void*)PixelBuffer, (void*)pixmap.GetPixels(), bytesC, bytesC);
  68. }
  69. /// <summary>
  70. /// 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.
  71. /// </summary>
  72. public unsafe Color GetSRGBPixel(VecI pos)
  73. {
  74. Half* ptr = (Half*)(PixelBuffer + (pos.X + pos.Y * Size.X) * BytesPerPixel);
  75. float a = (float)ptr[3];
  76. return (Color)new ColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
  77. }
  78. public void SetSRGBPixel(VecI pos, Color color)
  79. {
  80. drawingPaint.Color = color;
  81. DrawingSurface.Canvas.DrawPixel(pos.X, pos.Y, drawingPaint);
  82. }
  83. public unsafe bool IsFullyTransparent()
  84. {
  85. ulong* ptr = (ulong*)PixelBuffer;
  86. for (int i = 0; i < Size.X * Size.Y; i++)
  87. {
  88. // ptr[i] actually contains 4 16-bit floats. We only care about the first one which is alpha.
  89. // 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
  90. if ((ptr[i] & 0x1111_0000_0000_0000) != 0 && (ptr[i] & 0x1111_0000_0000_0000) != 0x8000_0000_0000_0000)
  91. return false;
  92. }
  93. return true;
  94. }
  95. public void SaveToDesktop(string filename = "savedSurface.png")
  96. {
  97. using var final = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb()));
  98. final.Canvas.DrawSurface(DrawingSurface, 0, 0);
  99. using (var snapshot = final.Snapshot())
  100. {
  101. using var stream = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename));
  102. using var png = snapshot.Encode();
  103. png.SaveTo(stream);
  104. }
  105. }
  106. private DrawingSurface CreateDrawingSurface()
  107. {
  108. var surface = PixiEditor.DrawingApi.Core.Surface.DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
  109. if (surface is null)
  110. throw new InvalidOperationException($"Could not create surface (Size:{Size})");
  111. return surface;
  112. }
  113. private static unsafe IntPtr CreateBuffer(int width, int height, int bytesPerPixel)
  114. {
  115. int byteC = width * height * bytesPerPixel;
  116. var buffer = Marshal.AllocHGlobal(byteC);
  117. Unsafe.InitBlockUnaligned((byte*)buffer, 0, (uint)byteC);
  118. return buffer;
  119. }
  120. public void Dispose()
  121. {
  122. if (disposed)
  123. return;
  124. disposed = true;
  125. drawingPaint.Dispose();
  126. nearestNeighborReplacingPaint.Dispose();
  127. Marshal.FreeHGlobal(PixelBuffer);
  128. GC.SuppressFinalize(this);
  129. }
  130. ~Surface()
  131. {
  132. Marshal.FreeHGlobal(PixelBuffer);
  133. }
  134. }