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); //Write image info before compressed data Span TexInfo = stackalloc byte[6]; BinaryPrimitives.WriteInt16LittleEndian(TexInfo, (short)Width); BinaryPrimitives.WriteInt16LittleEndian(TexInfo[2..], (short)Height); TexInfo[4] = (byte)img.Format; TexInfo[5] = (byte)img.Mipmaps; using MemoryStream stream = new(); stream.Write(TexInfo); DeflateStream compressor = new(stream, CompressionLevel.Optimal); compressor.Write(img.DataSpan()); compressor.Dispose(); SerializedData = stream.ToArray(); Raylib.UnloadImage(img); } Bitmap(Texture2D tex, byte[] serializedData) { SerializedData = serializedData; Texture = tex; } public Bitmap Copy() { Image ImgCopy = Raylib.LoadImageFromTexture(Texture); Bitmap Copy = new(Raylib.LoadTextureFromImage(ImgCopy), SerializedData.ToArray()); Raylib.UnloadImage(ImgCopy); return Copy; } 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.LoadImageFromMemory(".png", File.ReadAllBytes(path)), MaxW, MaxH); } public static Bitmap FromPNGMemory(byte[] memory, int MaxW = MaxWidth, int MaxH = MaxHeight) => new(Raylib.LoadImageFromMemory(".png", memory), MaxW, MaxH); public static Bitmap Deserialize(ReadOnlySpan span, int MaxW = MaxWidth, int MaxH = MaxHeight, Image? fallbackImage = null) { int width = BinaryPrimitives.ReadInt16LittleEndian(span); int height = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(2, 2)); PixelFormat format = (PixelFormat)span[4]; int mipmaps = span[5]; bool Invalid = width <= 0 || height <= 0 || width > MaxW || height > MaxH || format != PixelFormat.UncompressedR8G8B8A8; //If the is somehow invalid, then no use in loading the rest. Either use fallback, or generate error checkerboard. if (Invalid && fallbackImage != null) return new Bitmap(Raylib.ImageCopy(fallbackImage.Value), MaxW, MaxH); Image img = Raylib.GenImageChecked(Math.Clamp(width, 8, MaxW), Math.Clamp(height, 8, MaxH), 4, 4, Color.Pink, Color.Black); if (Invalid) return new Bitmap(img); byte[] compressed = span.ToArray(); byte[] data = new byte[RaylibExt.TextureLength(width, height, format, mipmaps)]; { using MemoryStream input = new(compressed); input.Position += 6; using DeflateStream decompressor = new(input, CompressionMode.Decompress); decompressor.ReadExactly(data); } unsafe {fixed (void* dataPtr = data) { Texture2D tex = new() { Id = Rlgl.LoadTexture(dataPtr, width, height, format, mipmaps), Width = width, Height = height, Format = format, Mipmaps = mipmaps }; return new Bitmap(tex, compressed); }} } public byte[] Serialize() => SerializedData; bool disposed = false; public void Dispose() { if (disposed) return; Raylib.UnloadTexture(Texture); GC.SuppressFinalize(this); disposed = true; } ~Bitmap() => Dispose(); }