using ChunkyImageLib.DataHolders; using Drawie.Backend.Core; using Drawie.Backend.Core.Numerics; using Drawie.Backend.Core.Surfaces; using Drawie.Backend.Core.Surfaces.PaintImpl; using Drawie.Numerics; namespace ChunkyImageLib; public class Chunk : IDisposable { private static volatile int chunkCounter = 0; /// /// The number of chunks that haven't yet been returned (includes garbage collected chunks). /// Used in tests to make sure that all chunks are disposed. /// public static int ChunkCounter => chunkCounter; private bool returned = false; /// /// The surface of the chunk /// public Surface Surface { get { if (returned) { throw new ObjectDisposedException("Chunk has been disposed"); } return internalSurface; } } /// /// The size of the chunk /// public VecI PixelSize { get; } /// /// The resolution of the chunk /// public ChunkResolution Resolution { get; } public bool Disposed => returned; private Surface internalSurface; private Chunk(ChunkResolution resolution) { int size = resolution.PixelSize(); Resolution = resolution; PixelSize = new(size, size); internalSurface = new Surface(PixelSize); } /// /// Tries to take a chunk with the from the pool, or creates a new one /// public static Chunk Create(ChunkResolution resolution = ChunkResolution.Full) { var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution); chunk.returned = false; Interlocked.Increment(ref chunkCounter); return chunk; } /// /// Draw's on the of the chunk /// /// The destination for the /// The paint to use while drawing public void DrawChunkOn(DrawingSurface surface, VecI pos, Paint? paint = null) { surface.Canvas.DrawSurface(Surface.DrawingSurface, pos.X, pos.Y, paint); } public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null) { RectI? bounds = null; if (returned) return bounds; if (passedSearchRegion is not null && !new RectI(VecI.Zero, Surface.Size).ContainsInclusive(passedSearchRegion.Value)) throw new ArgumentException("Passed search region lies outside of the chunk's surface", nameof(passedSearchRegion)); RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size); ulong* ptr = (ulong*)Surface.PixelBuffer; for (int y = searchRegion.Top; y < searchRegion.Bottom; y++) { for (int x = searchRegion.Left; x < searchRegion.Right; x++) { int i = y * Surface.Size.X + x; // ptr[i] actually contains 4 16-bit floats. We only care about the first one which is alpha. // 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 if ((ptr[i] & 0x1111_0000_0000_0000) != 0 && (ptr[i] & 0x1111_0000_0000_0000) != 0x8000_0000_0000_0000) { bounds ??= new RectI(x, y, 1, 1); bounds = bounds.Value.Union(new RectI(x, y, 1, 1)); } } } return bounds; } /// /// Returns the chunk back to the pool /// public void Dispose() { if (returned) return; Interlocked.Decrement(ref chunkCounter); Surface.DrawingSurface.Canvas.Clear(); ChunkPool.Instance.Push(this); returned = true; } }