summaryrefslogtreecommitdiff
path: root/Bitmap.cs
blob: 6922fae4c1597eb48f2d1e944f70e19725cbca6e (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);

        //Write image info before compressed data
        Span<byte> 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<byte> span, int MaxW = MaxWidth, int MaxH = MaxHeight, Image? fallbackImage = null)
    {
        int width = BinaryPrimitives.ReadInt16LittleEndian(span[..2]);
        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[6..].ToArray();
        byte[] data = new byte[RaylibExt.TextureLength(width, height, format, mipmaps)];
        {
            using MemoryStream input = new(compressed);
            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();
}