summaryrefslogtreecommitdiff
path: root/Bitmap.cs
blob: fc6fa400c025286e9a7e9faaae92cfc33c902673 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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<byte> imgData;
        unsafe {
            imgData = new(img.Data, imgSize);
        }
        using MemoryStream stream = new();

        //Write width and height before compressed data
        Span<byte> 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<byte> 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();
}