1
+
using System;
2
+
using System.IO;
3
+
using System.Runtime.InteropServices;
4
+
5
+
6
+
// my bad C# translation of this - https://github.com/phoboslab/qoi/blob/master/qoi.h
7
+
namespace QoiFileTypeNet {
8
+
[StructLayout(LayoutKind.Sequential)]
9
+
internal struct QoiRgba : IEquatable<QoiRgba> {
10
+
public byte r;
11
+
public byte g;
12
+
public byte b;
13
+
public byte a;
14
+
15
+
public override bool Equals(object obj) => obj is QoiRgba other && this.Equals(other);
16
+
17
+
public bool Equals(QoiRgba other) {
18
+
return this.r == other.r &&
19
+
this.g == other.g &&
20
+
this.b == other.b &&
21
+
this.a == other.a;
22
+
}
23
+
24
+
public override int GetHashCode() => (r, g, b, a).GetHashCode();
25
+
26
+
public static bool operator ==(QoiRgba a, QoiRgba b) => a.Equals(b);
27
+
public static bool operator !=(QoiRgba a, QoiRgba b) => !a.Equals(b);
28
+
}
29
+
30
+
internal struct QoiDesc {
31
+
public const int QOI_SRGB = 0x00;
32
+
public const int QOI_SRGB_LINEAR_ALPHA = 0x01;
33
+
public const int QOI_LINEAR = 0x0F;
34
+
35
+
public int width;
36
+
public int height;
37
+
public int channels;
38
+
public int colorspace;
39
+
}
40
+
41
+
internal static class QoiFile {
42
+
private const int QOI_INDEX = 0x00; // 00xxxxxx
43
+
private const int QOI_RUN_8 = 0x40; // 010xxxxx
44
+
private const int QOI_RUN_16 = 0x60; // 011xxxxx
45
+
private const int QOI_DIFF_8 = 0x80; // 10xxxxxx
46
+
private const int QOI_DIFF_16 = 0xC0; // 110xxxxx
47
+
private const int QOI_DIFF_24 = 0xE0; // 1110xxxx
48
+
private const int QOI_COLOR = 0xF0; // 1111xxxx
49
+
50
+
private const int QOI_MASK_2 = 0xC0; // 11000000
51
+
private const int QOI_MASK_3 = 0xE0; // 11100000
52
+
private const int QOI_MASK_4 = 0xF0; // 11110000
53
+
54
+
private const int QOI_MAGIC = (((int)'q') << 24 | ((int)'o') << 16 | ((int)'i') << 8 | ((int)'f'));
55
+
56
+
private const int QOI_HEADER_SIZE = 14;
57
+
private const int QOI_PADDING = 4;
58
+
59
+
private static int ColorHash(ref QoiRgba c) {
60
+
return c.r ^ c.g ^ c.b ^ c.a;
61
+
}
62
+
63
+
private static uint SwapBytes(uint x) {
64
+
return ((x & 0x000000ff) << 24) |
65
+
((x & 0x0000ff00) << 8) |
66
+
((x & 0x00ff0000) >> 8) |
67
+
((x & 0xff000000) >> 24);
68
+
}
69
+
70
+
private static int SwapBytes(int x) {
71
+
return (int)SwapBytes((uint)x);
72
+
}
73
+
74
+
public static QoiRgba[] Load(Stream input, ref QoiDesc desc) {
75
+
QoiRgba[] result = null;
76
+
desc.width = 0;
77
+
desc.height = 0;
78
+
79
+
int fileSize = (int)input.Length;
80
+
if (fileSize < (QOI_HEADER_SIZE + QOI_PADDING)) {
81
+
return result;
82
+
}
83
+
84
+
using (BinaryReader reader = new BinaryReader(input)) {
85
+
uint headerMagic = SwapBytes(reader.ReadUInt32());
86
+
if (headerMagic != QOI_MAGIC) {
87
+
return result;
88
+
}
89
+
90
+
desc.width = SwapBytes(reader.ReadInt32());
91
+
desc.height = SwapBytes(reader.ReadInt32());
92
+
desc.channels = reader.ReadByte();
93
+
desc.colorspace = reader.ReadByte();
94
+
95
+
if (desc.width == 0 || desc.height == 0 || desc.channels < 3 || desc.channels > 4) {
96
+
return null;
97
+
}
98
+
99
+
int numPixels = (int)(desc.width * desc.height);
100
+
result = new QoiRgba[numPixels];
101
+
102
+
QoiRgba px = new QoiRgba { r = 0, g = 0, b = 0, a = 255 };
103
+
QoiRgba[] index = new QoiRgba[64];
104
+
105
+
int run = 0;
106
+
int chunksLen = fileSize - QOI_PADDING;
107
+
for (int i = 0; i < numPixels; ++i) {
108
+
if (run > 0) {
109
+
run--;
110
+
} else if (reader.BaseStream.Position < chunksLen) {
111
+
byte b1 = reader.ReadByte();
112
+
113
+
if ((b1 & QOI_MASK_2) == QOI_INDEX) {
114
+
px = index[b1 ^ QOI_INDEX];
115
+
} else if ((b1 & QOI_MASK_3) == QOI_RUN_8) {
116
+
run = b1 & 0x1F;
117
+
} else if ((b1 & QOI_MASK_3) == QOI_RUN_16) {
118
+
byte b2 = reader.ReadByte();
119
+
run = (((b1 & 0x1f) << 8) | (b2)) + 32;
120
+
} else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) {
121
+
px.r += (byte)(((b1 >> 4) & 0x03) - 2);
122
+
px.g += (byte)(((b1 >> 2) & 0x03) - 2);
123
+
px.b += (byte)((b1 & 0x03) - 2);
124
+
} else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) {
125
+
byte b2 = reader.ReadByte();
126
+
px.r += (byte)((b1 & 0x1f) - 16);
127
+
px.g += (byte)((b2 >> 4) - 8);
128
+
px.b += (byte)((b2 & 0x0f) - 8);
129
+
} else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) {
130
+
byte b2 = reader.ReadByte();
131
+
byte b3 = reader.ReadByte();
132
+
px.r += (byte)((((b1 & 0x0f) << 1) | (b2 >> 7)) - 16);
133
+
px.g += (byte)(((b2 & 0x7c) >> 2) - 16);
134
+
px.b += (byte)((((b2 & 0x03) << 3) | ((b3 & 0xe0) >> 5)) - 16);
135
+
px.a += (byte)((b3 & 0x1f) - 16);
136
+
} else if ((b1 & QOI_MASK_4) == QOI_COLOR) {
137
+
if ((b1 & 8) == 8) { px.r = reader.ReadByte(); }
138
+
if ((b1 & 4) == 4) { px.g = reader.ReadByte(); }
139
+
if ((b1 & 2) == 2) { px.b = reader.ReadByte(); }
140
+
if ((b1 & 1) == 1) { px.a = reader.ReadByte(); }
141
+
}
142
+
143
+
index[ColorHash(ref px) % 64] = px;
144
+
}
145
+
146
+
result[i] = px;
147
+
}
148
+
}
149
+
150
+
return result;
151
+
}
152
+
153
+
public static void Save(Stream output, ref QoiDesc desc, QoiRgba[] pixels) {
154
+
BinaryWriter writer = new BinaryWriter(output);
155
+
156
+
writer.Write(SwapBytes(QOI_MAGIC));
157
+
writer.Write(SwapBytes(desc.width));
158
+
writer.Write(SwapBytes(desc.height));
159
+
writer.Write((byte)desc.channels);
160
+
writer.Write((byte)desc.colorspace);
161
+
162
+
QoiRgba px = new QoiRgba { r = 0, g = 0, b = 0, a = 255 };
163
+
QoiRgba pxPrev = px;
164
+
QoiRgba[] index = new QoiRgba[64];
165
+
166
+
int run = 0;
167
+
int numPixels = desc.width * desc.height;
168
+
for (int i = 0; i < numPixels; ++i) {
169
+
px = pixels[i];
170
+
171
+
if (px == pxPrev) {
172
+
run++;
173
+
}
174
+
175
+
if (run > 0 && (run == 0x2020 || px != pxPrev || i == (numPixels - 1))) {
176
+
if (run < 33) {
177
+
run -= 1;
178
+
writer.Write((byte)(QOI_RUN_8 | run));
179
+
} else {
180
+
run -= 33;
181
+
writer.Write((byte)(QOI_RUN_16 | run >> 8));
182
+
writer.Write((byte)run);
183
+
}
184
+
run = 0;
185
+
}
186
+
187
+
if (px != pxPrev) {
188
+
int indexPos = ColorHash(ref px) % 64;
189
+
190
+
if (index[indexPos] == px) {
191
+
writer.Write((byte)(QOI_INDEX | indexPos));
192
+
} else {
193
+
index[indexPos] = px;
194
+
195
+
int vr = px.r - pxPrev.r;
196
+
int vg = px.g - pxPrev.g;
197
+
int vb = px.b - pxPrev.b;
198
+
int va = px.a - pxPrev.a;
199
+
200
+
if (vr > -17 && vr < 16 &&
201
+
vg > -17 && vg < 16 &&
202
+
vb > -17 && vb < 16 &&
203
+
va > -17 && va < 16) {
204
+
if (va == 0 &&
205
+
vr > -3 && vr < 2 &&
206
+
vg > -3 && vg < 2 &&
207
+
vb > -3 && vb < 2) {
208
+
writer.Write((byte)(QOI_DIFF_8 | ((vr + 2) << 4) | (vg + 2) << 2 | (vb + 2)));
209
+
} else if (va == 0 &&
210
+
vr > -17 && vr < 16 &&
211
+
vg > -9 && vg < 8 &&
212
+
vb > -9 && vb < 8 ) {
213
+
writer.Write((byte)(QOI_DIFF_16 | (vr + 16)));
214
+
writer.Write((byte)((vg + 8) << 4 | (vb + 8)));
215
+
} else {
216
+
writer.Write((byte)(QOI_DIFF_24 | (vr + 16) >> 1));
217
+
writer.Write((byte)((vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3));
218
+
writer.Write((byte)((vb + 16) << 5 | (va + 16)));
219
+
}
220
+
} else {
221
+
writer.Write((byte)(QOI_COLOR | ((vr != 0) ? 8 : 0) | ((vg != 0) ? 4 : 0) | ((vb != 0) ? 2 : 0) | ((va != 0) ? 1 : 0)));
222
+
if (vr != 0) { writer.Write(px.r); }
223
+
if (vg != 0) { writer.Write(px.g); }
224
+
if (vb != 0) { writer.Write(px.b); }
225
+
if (va != 0) { writer.Write(px.a); }
226
+
}
227
+
}
228
+
}
229
+
230
+
pxPrev = px;
231
+
}
232
+
233
+
for (int i = 0; i < QOI_PADDING; ++i) {
234
+
writer.Write((byte)0);
235
+
}
236
+
}
237
+
}
238
+
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4