using System.Buffers.Binary; using System.IO.Compression; using System.Numerics; using Raylib_cs; namespace OnekoOnline; class Bitmap : IDisposable { public readonly int Width; public readonly int Height; public readonly Texture2D Texture; readonly byte[] SerializedData; const int MaxWidth = 256; const int MaxHeight = 256; public Bitmap(Image img, int MaxW = 256, int MaxH = 256) { Width = img.Width; Height = img.Height; //Crop image if too big if (Width > MaxW || Height > MaxH) Raylib.ImageCrop(ref img, new Rectangle(0f, 0f, MaxW, MaxH)); Texture = Raylib.LoadTextureFromImage(img); //Get Data for Serialization Color[] colors = new Color[Width*Height]; int i = 0; for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { colors[i] = Raylib.GetImageColor(img, x, y); i++; } } byte[] data = new byte[(colors.Length*4) + (sizeof(short)*2)]; BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(0, sizeof(short)), (short)Width); BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(sizeof(short), sizeof(short)), (short)Height); int position = sizeof(short)*2; for (int c = 0; c < colors.Length; c++) { data[position] = colors[c].R; data[position+1] = colors[c].G; data[position+2] = colors[c].B; data[position+3] = colors[c].A; position += 4; } using MemoryStream stream = new(); using DeflateStream compressor = new(stream, CompressionLevel.Optimal); compressor.Write(data); compressor.Close(); SerializedData = stream.ToArray(); Raylib.UnloadImage(img); } Bitmap(Image img, byte[] serializedData) { Width = img.Width; Height = img.Height; SerializedData = serializedData; Texture = Raylib.LoadTextureFromImage(img); Raylib.UnloadImage(img); } 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)); byte[] memory = File.ReadAllBytes(path); return FromPNGMemory(memory, 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) { byte[] compressed = span.ToArray(); byte[] data; { using MemoryStream input = new(compressed); using MemoryStream output = new(); using DeflateStream decompressor = new(input, CompressionMode.Decompress); decompressor.CopyTo(output); data = output.ToArray(); } int width = BinaryPrimitives.ReadInt16LittleEndian(data.AsSpan(0, sizeof(short))); int height = BinaryPrimitives.ReadInt16LittleEndian(data.AsSpan(sizeof(short), sizeof(short))); bool TooBig = width > MaxW || height > MaxH; if (TooBig) { width = Math.Clamp(width, 0, MaxW); height = Math.Clamp(height, 0, MaxH); if (fallbackImage != null) return FromPNGMemory(fallbackImage, MaxW, MaxH); } Image img = Raylib.GenImageChecked(width, height, 4, 4, Color.PINK, Color.BLACK); if (TooBig) return new Bitmap(img); int i = sizeof(short)*2; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Color color = new(data[i], data[i+1], data[i+2], data[i+3]); Raylib.ImageDrawPixel(ref img, x, y, color); i += 4; } } return new Bitmap(img, 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(); }