Skip to content

Commit 1919a34

Browse files
More WIP on GIF decoder
1 parent a72a99a commit 1919a34

File tree

3 files changed

+137
-39
lines changed

3 files changed

+137
-39
lines changed

MCGalaxy/util/Imaging/GifDecoder.cs

Lines changed: 130 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,35 @@ public override SimpleBitmap Decode(byte[] src) {
4040
if (!DetectHeader(src)) Fail("sig invalid");
4141
AdvanceOffset(gif87Sig.Length);
4242

43-
ReadGlobalHeader(src);
44-
ReadMarkers(src);
45-
Fail("GIF decoder unfinished");
46-
return null;
43+
SimpleBitmap bmp = new SimpleBitmap();
44+
ReadGlobalHeader(src, bmp);
45+
ReadMarkers(src, bmp);
46+
return bmp;
4747
}
4848

4949
const int LOGICAL_DESC_SIZE = 7;
50-
void ReadGlobalHeader(byte[] src) {
50+
void ReadGlobalHeader(byte[] src, SimpleBitmap bmp) {
5151
// read logical screen descriptor
5252
int offset = AdvanceOffset(LOGICAL_DESC_SIZE);
5353

54-
ushort width = MemUtils.ReadU16_LE(src, offset + 0);
55-
ushort height = MemUtils.ReadU16_LE(src, offset + 2);
54+
bmp.Width = MemUtils.ReadU16_LE(src, offset + 0);
55+
bmp.Height = MemUtils.ReadU16_LE(src, offset + 2);
5656

5757
byte flags = src[offset + 4];
5858
byte bgIndex = src[offset + 5];
5959
// src[offset + 6] is pixel aspect ratio - not used
6060

6161
bool hasGlobalPal = (flags & 0x80) != 0;
62-
int globalPalSize = 1 << ((flags & 0x7) + 1);
63-
if (hasGlobalPal) ReadGlobalPalette(src, globalPalSize);
62+
if (hasGlobalPal)
63+
globalPal = ReadPalette(src, flags);
64+
if (hasGlobalPal && bgIndex < globalPal.Length)
65+
bgColor = globalPal[bgIndex];
6466

65-
if (hasGlobalPal && bgIndex < globalPalSize)
66-
bgColor = globalPal[bgIndex];
67+
bmp.AllocatePixels();
6768
}
6869

69-
void ReadGlobalPalette(byte[] src, int size) {
70+
Pixel[] ReadPalette(byte[] src, byte flags) {
71+
int size = 1 << ((flags & 0x7) + 1);
7072
Pixel[] pal = new Pixel[size];
7173
int offset = AdvanceOffset(3 * size);
7274

@@ -77,15 +79,15 @@ void ReadGlobalPalette(byte[] src, int size) {
7779
pal[i].B = src[offset++];
7880
pal[i].A = 255;
7981
}
80-
globalPal = pal;
82+
return pal;
8183
}
8284

8385

8486
const byte MARKER_EXTENSION = 0x21;
8587
const byte MARKER_IMAGE_END = 0x3B;
8688
const byte MARKER_IMAGE_BEG = 0x2C;
8789

88-
void ReadMarkers(byte[] src) {
90+
void ReadMarkers(byte[] src, SimpleBitmap bmp) {
8991
for (;;)
9092
{
9193
int offset = AdvanceOffset(1);
@@ -96,7 +98,7 @@ void ReadMarkers(byte[] src) {
9698
ReadExtension(src);
9799
break;
98100
case MARKER_IMAGE_BEG:
99-
ReadImage(src);
101+
ReadImage(src, bmp);
100102
return; // NOTE: stops reading at first frame
101103
case MARKER_IMAGE_END:
102104
return;
@@ -151,8 +153,13 @@ void SkipSubBlocks(byte[] src) {
151153
}
152154
}
153155

156+
const int MAX_CODE_LEN = 12;
157+
const int MAX_CODES = 1 << MAX_CODE_LEN;
158+
byte curSubBlockLeft;
159+
bool subBlocksEnd;
160+
int subBlocksOffset;
154161

155-
void ReadImage(byte[] src) {
162+
void ReadImage(byte[] src, SimpleBitmap bmp) {
156163
// Read image descriptor header
157164
int offset = AdvanceOffset(2 + 2 + 2 + 2 + 1);
158165

@@ -162,68 +169,156 @@ void ReadImage(byte[] src) {
162169
ushort imageH = MemUtils.ReadU16_LE(src, offset + 6);
163170
byte flags = src[offset + 8];
164171

172+
if ((flags & 0x40) != 0) Fail("Interlaced GIF unsupported");
173+
if (imageX + imageW > bmp.Width) Fail("Invalid X dimensions");
174+
if (imageY + imageH > bmp.Height) Fail("Invalid Y dimensions");
175+
176+
bool hasLocalPal = (flags & 0x80) != 0;
177+
Pixel[] localPal = null;
178+
if (hasLocalPal)
179+
localPal = ReadPalette(src, flags);
180+
181+
Pixel[] pal = localPal ?? globalPal;
182+
int dst_index = 0;
183+
165184
// Read image data
166185
offset = AdvanceOffset(1);
167186
byte minCodeSize = src[offset];
187+
if (minCodeSize >= MAX_CODE_LEN) Fail("codesize too long");
188+
189+
curSubBlockLeft = 0;
190+
subBlocksEnd = false;
168191

169192
// Init LZW variables
170193
int codeLen = minCodeSize + 1;
171194
int codeMask = (1 << codeLen) - 1;
172195
int clearCode = (1 << minCodeSize) + 0;
173196
int stopCode = (1 << minCodeSize) + 1;
174-
int dictEnd;
197+
int prevCode, availCode;
175198
DictEntry[] dict = new DictEntry[1 << codeLen];
176199

177200
// Bit buffer state
178201
uint bufVal = 0;
179202
int bufLen = 0;
180203

181204
// Spec says clear code _should_ be sent first, but not required
182-
for (dictEnd = 0; dictEnd < (1 << minCodeSize); dictEnd++)
205+
for (availCode = 0; availCode < (1 << minCodeSize); availCode++)
183206
{
184-
dict[dictEnd].value = (byte)dictEnd;
185-
dict[dictEnd].prev = -1;
186-
dict[dictEnd].len = 1;
207+
dict[availCode].value = (byte)availCode;
208+
dict[availCode].prev = -1;
209+
dict[availCode].len = 1;
187210
}
188-
dictEnd += 2; // "clear code" and "stop code" entries
211+
212+
availCode += 2; // "clear code" and "stop code" entries
213+
prevCode = -1;
189214

190215
for (;;)
191216
{
192-
int code = 0;
193-
217+
// Refill buffer when needed
194218
if (bufLen < codeLen) {
219+
int read;
220+
while (bufLen <= 24 && (read = ReadNextByte()) >= 0) {
221+
bufVal |= (uint)read << bufLen;
222+
bufLen += 8;
223+
}
195224

225+
if (bufLen < codeLen) Fail("not enough bits for code");
196226
}
197227

198-
code = (int)(bufVal & codeMask);
199-
bufVal >>= codeMask;
200-
bufLen -= codeLen;
228+
int code = (int)(bufVal & codeMask);
229+
bufVal >>= codeLen;
230+
bufLen -= codeLen;
201231

202232
if (code == clearCode) {
203233
codeLen = minCodeSize + 1;
204234
codeMask = (1 << codeLen) - 1;
205235

206236
// Clear dictionary
207-
for (dictEnd = 0; dictEnd < (1 << minCodeSize); dictEnd++)
237+
for (availCode = 0; availCode < (1 << minCodeSize); availCode++)
208238
{
209-
dict[dictEnd].value = (byte)dictEnd;
210-
dict[dictEnd].prev = -1;
211-
dict[dictEnd].len = 1;
239+
dict[availCode].value = (byte)availCode;
240+
dict[availCode].prev = -1;
241+
dict[availCode].len = 1;
212242
}
213-
dictEnd += 2; // "clear code" and "stop code" entries
243+
244+
availCode += 2; // "clear code" and "stop code" entries
245+
prevCode = -1;
214246
} else if (code == stopCode) {
215247
break;
216248
}
249+
250+
if (code > availCode) Fail("invalid code");
251+
252+
// Add new entry to code table unless it's full
253+
// GIF spec allows this as per 'deferred clear codes'
254+
if (prevCode >= 0 && availCode < MAX_CODES) {
255+
int firstCode = code == availCode ? prevCode : code;
256+
// Follow chain back to find first value
257+
// TODO optimise this...
258+
while (dict[firstCode].prev != -1)
259+
{
260+
firstCode = dict[firstCode].prev;
261+
}
262+
263+
dict[availCode].value = dict[firstCode].value;
264+
dict[availCode].prev = (short)prevCode;
265+
dict[availCode].len = (short)(dict[prevCode].len + 1);
266+
267+
// Check if inserted code in last free entry of table
268+
// If this is the case, then the table is immediately expanded
269+
if (availCode == codeMask && availCode != (MAX_CODES - 1)) {
270+
codeLen++;
271+
codeMask = (1 << codeLen) - 1;
272+
Array.Resize(ref dict, 1 << codeLen);
273+
}
274+
availCode++;
275+
}
276+
277+
prevCode = code;
278+
// TODO output code
279+
280+
// "top" entry is actually last entry in chain
281+
int chain_len = dict[code].len;
282+
for (int i = chain_len - 1; i >= 0; i--)
283+
{
284+
int index = dst_index + i;
285+
byte palIndex = dict[code].value;
286+
287+
//int localX = index % imageW;
288+
//int localY = index / imageW;
289+
int globalX = imageX + (index % imageW);
290+
int globalY = imageY + (index / imageW);
291+
bmp.pixels[globalY * bmp.Width + globalX] = pal[palIndex];
292+
293+
code = dict[code].prev;
294+
}
295+
296+
dst_index += chain_len;
217297
}
218-
219-
//SkipSubBlocks(src);
220-
Fail("GIF decoder unfinished");
221298
}
222299

223300
struct DictEntry
224301
{
225302
public byte value;
226303
public short prev, len;
227304
}
305+
306+
int ReadNextByte() {
307+
if (curSubBlockLeft == 0) {
308+
if (subBlocksEnd) return -1;
309+
310+
subBlocksOffset = AdvanceOffset(1);
311+
curSubBlockLeft = buf_data[subBlocksOffset++];
312+
313+
// If sub block length is 0, then reached end of sub blocks
314+
if (curSubBlockLeft == 0) {
315+
subBlocksEnd = true;
316+
return -1;
317+
}
318+
}
319+
320+
curSubBlockLeft--;
321+
return buf_data[subBlocksOffset++];
322+
}
228323
}
229324
}

MCGalaxy/util/Imaging/ImageDecoder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ public sealed class SimpleBitmap //: IBitmap2D
2626
{
2727
public int Width, Height;
2828
public Pixel[] pixels;
29+
30+
public void AllocatePixels() {
31+
pixels = new Pixel[Width * Height];
32+
}
2933
}
3034

3135
public abstract class ImageDecoder

MCGalaxy/util/Imaging/PngDecoder.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,9 @@ public override SimpleBitmap Decode(byte[] src) {
108108
if (src[offset + 11] != 0) Fail("Filter");
109109
if (src[offset + 12] != 0) Fail("Interlaced unsupported");
110110

111-
bytesPerPixel = ((samplesPerPixel[colorspace] * bitsPerSample) + 7) >> 3;
112-
scanline_size = ((samplesPerPixel[colorspace] * bitsPerSample * bmp.Width) + 7) >> 3;
113-
114-
bmp.pixels = new Pixel[bmp.Width * bmp.Height];
111+
bytesPerPixel = ((samplesPerPixel[colorspace] * bitsPerSample) + 7) >> 3;
112+
scanline_size = ((samplesPerPixel[colorspace] * bitsPerSample * bmp.Width) + 7) >> 3;
113+
bmp.AllocatePixels();
115114
} break;
116115

117116
// 11.2.3 PLTE Palette

0 commit comments

Comments
 (0)