Chunk.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. using ChunkyImageLib.DataHolders;
  2. using Drawie.Backend.Core;
  3. using Drawie.Backend.Core.Numerics;
  4. using Drawie.Backend.Core.Surfaces;
  5. using Drawie.Backend.Core.Surfaces.PaintImpl;
  6. using Drawie.Numerics;
  7. namespace ChunkyImageLib;
  8. public class Chunk : IDisposable
  9. {
  10. private static volatile int chunkCounter = 0;
  11. /// <summary>
  12. /// The number of chunks that haven't yet been returned (includes garbage collected chunks).
  13. /// Used in tests to make sure that all chunks are disposed.
  14. /// </summary>
  15. public static int ChunkCounter => chunkCounter;
  16. private bool returned = false;
  17. /// <summary>
  18. /// The surface of the chunk
  19. /// </summary>
  20. public Surface Surface
  21. {
  22. get
  23. {
  24. if (returned)
  25. {
  26. throw new ObjectDisposedException("Chunk has been disposed");
  27. }
  28. return internalSurface;
  29. }
  30. }
  31. /// <summary>
  32. /// The size of the chunk
  33. /// </summary>
  34. public VecI PixelSize { get; }
  35. /// <summary>
  36. /// The resolution of the chunk
  37. /// </summary>
  38. public ChunkResolution Resolution { get; }
  39. public bool Disposed => returned;
  40. private Surface internalSurface;
  41. private Chunk(ChunkResolution resolution)
  42. {
  43. int size = resolution.PixelSize();
  44. Resolution = resolution;
  45. PixelSize = new(size, size);
  46. internalSurface = new Surface(PixelSize);
  47. }
  48. /// <summary>
  49. /// Tries to take a chunk with the <paramref name="resolution"/> from the pool, or creates a new one
  50. /// </summary>
  51. public static Chunk Create(ChunkResolution resolution = ChunkResolution.Full)
  52. {
  53. var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution);
  54. chunk.returned = false;
  55. Interlocked.Increment(ref chunkCounter);
  56. return chunk;
  57. }
  58. /// <summary>
  59. /// Draw's on the <see cref="Surface"/> of the chunk
  60. /// </summary>
  61. /// <param name="pos">The destination for the <paramref name="surface"/></param>
  62. /// <param name="paint">The paint to use while drawing</param>
  63. public void DrawChunkOn(DrawingSurface surface, VecI pos, Paint? paint = null)
  64. {
  65. surface.Canvas.DrawSurface(Surface.DrawingSurface, pos.X, pos.Y, paint);
  66. }
  67. public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
  68. {
  69. RectI? bounds = null;
  70. if (returned)
  71. return bounds;
  72. if (passedSearchRegion is not null && !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value))
  73. throw new ArgumentException("Passed search region lies outside of the chunk's surface", nameof(passedSearchRegion));
  74. RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size);
  75. ulong* ptr = (ulong*)Surface.PixelBuffer;
  76. for (int y = searchRegion.Top; y < searchRegion.Bottom; y++)
  77. {
  78. for (int x = searchRegion.Left; x < searchRegion.Right; x++)
  79. {
  80. int i = y * Surface.Size.X + x;
  81. // ptr[i] actually contains 4 16-bit floats. We only care about the first one which is alpha.
  82. // 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
  83. if ((ptr[i] & 0x1111_0000_0000_0000) != 0 && (ptr[i] & 0x1111_0000_0000_0000) != 0x8000_0000_0000_0000)
  84. {
  85. bounds ??= new RectI(x, y, 1, 1);
  86. bounds = bounds.Value.Union(new RectI(x, y, 1, 1));
  87. }
  88. }
  89. }
  90. return bounds;
  91. }
  92. /// <summary>
  93. /// Returns the chunk back to the pool
  94. /// </summary>
  95. public void Dispose()
  96. {
  97. if (returned)
  98. return;
  99. Interlocked.Decrement(ref chunkCounter);
  100. Surface.DrawingSurface.Canvas.Clear();
  101. ChunkPool.Instance.Push(this);
  102. returned = true;
  103. }
  104. }