using System.Buffers.Binary; using System.IO.Compression; using Raylib_cs; namespace OnekoOnline; class Bitmap : IDisposable { public int Width => Texture.Width; public int Height => Texture.Height; public readonly Texture2D Texture; readonly byte[] SerializedData; const int MaxWidth = 256; const int MaxHeight = 256; public Bitmap(Image img, int MaxW = MaxWidth, int MaxH = MaxHeight) { //Crop image if too big if (img.Width > MaxW || img.Height > MaxH) { Raylib.ImageCrop(ref img, new Rectangle(0f, 0f, MaxW, MaxH)); } Raylib.ImageFormat(ref img, PixelFormat.UncompressedR8G8B8A8); Texture = Raylib.LoadTextureFromImage(img); //Get Data for Serialization int imgSize = Raylib.GetPixelDataSize(img.Width, img.Height, PixelFormat.UncompressedR8G8B8A8); Span imgData; unsafe { imgData = new(img.Data, imgSize); } using MemoryStream stream = new(); //Write width and height before compressed data Span WidthHeight = stackalloc byte[4]; BinaryPrimitives.WriteInt16LittleEndian(WidthHeight, (short)Width); BinaryPrimitives.WriteInt16LittleEndian(WidthHeight[2..], (short)Height); stream.Write(WidthHeight); using DeflateStream compressor = new(stream, CompressionLevel.Optimal); compressor.Write(imgData); compressor.Close(); SerializedData = stream.ToArray(); Raylib.UnloadImage(img); } Bitmap(Texture2D tex, byte[] serializedData) { SerializedData = serializedData; Texture = tex; } public static Bitmap FromFile(string path, int MaxW = MaxWidth, int MaxH = MaxHeight) { if (!File.Exists(path) || new FileInfo(path).Length > 40000 || !path.Contains(".png")) return new Bitmap(Raylib.GenImageChecked(32, 32, 4, 4, Color.Black, Color.Pink)); return new Bitmap(Raylib.LoadImage(path), MaxW, MaxH); } public static Bitmap FromPNGMemory(byte[] memory, int MaxW = MaxWidth, int MaxH = MaxHeight) { return new Bitmap(Raylib.LoadImageFromMemory(".png", memory), MaxW, MaxH); } public static Bitmap Deserialize(ReadOnlySpan span, int MaxW = MaxWidth, int MaxH = MaxHeight, byte[]? fallbackImage = null) { int width = BinaryPrimitives.ReadInt16LittleEndian(span[..2]); int height = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(2, 2)); bool Invalid = width <=0 || height <= 0 || width > MaxW || height > MaxH; width = Math.Clamp(width, 1, MaxW); height = Math.Clamp(height, 1, MaxH); //If the width and height are somehow wrong, then no use in loading the rest. if (Invalid && fallbackImage != null) return FromPNGMemory(fallbackImage, MaxW, MaxH); Image img = Raylib.GenImageChecked(width, height, 4, 4, Color.Pink, Color.Black); Raylib.ImageFormat(ref img, PixelFormat.UncompressedR8G8B8A8); if (Invalid) return new Bitmap(img); byte[] compressed = span[4..].ToArray(); byte[] data = new byte[Raylib.GetPixelDataSize(width, height, PixelFormat.UncompressedR8G8B8A8)]; { using MemoryStream input = new(compressed); using DeflateStream decompressor = new(input, CompressionMode.Decompress); decompressor.ReadExactly(data); } Texture2D tex = Raylib.LoadTextureFromImage(img); Raylib.UpdateTexture(tex, data); Raylib.UnloadImage(img); return new Bitmap(tex, compressed); } public byte[] Serialize() { return SerializedData; } bool disposed = false; public void Dispose() { if (disposed) return; Raylib.UnloadTexture(Texture); GC.SuppressFinalize(this); disposed = true; } ~Bitmap() => Dispose(); }