Skip to content

Commit cfd6ca6

Browse files
JPEG: Now properly reads blocks of MCUs in 3 channel images
1 parent d800bb2 commit cfd6ca6

File tree

4 files changed

+99
-66
lines changed

4 files changed

+99
-66
lines changed

MCGalaxy/Levels/Level.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ internal void Init(string name, ushort width, ushort height, ushort length) {
7373
this.name = name; MapName = name.ToLower();
7474
BlockDB = new BlockDB(this);
7575

76-
ChunksX = Utils.CeilDiv16(width);
77-
ChunksY = Utils.CeilDiv16(height);
78-
ChunksZ = Utils.CeilDiv16(length);
76+
ChunksX = Utils.CeilDiv(width, 16);
77+
ChunksY = Utils.CeilDiv(height, 16);
78+
ChunksZ = Utils.CeilDiv(length, 16);
7979
if (CustomBlocks == null) CustomBlocks = new byte[ChunksX * ChunksY * ChunksZ][];
8080

8181
spawnx = (ushort)(width / 2);

MCGalaxy/util/Imaging/JpegDecoder.cs

Lines changed: 89 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public class JpegDecoder : ImageDecoder
3131
HuffmanTable[] ac_huff_tables = new HuffmanTable[4];
3232
HuffmanTable[] dc_huff_tables = new HuffmanTable[4];
3333
JpegComponent[] comps;
34+
byte lowestHor = 1;
35+
byte lowestVer = 1;
3436

3537
public static bool DetectHeader(byte[] data) {
3638
return MatchesSignature(data, jfifSig)
@@ -92,6 +94,7 @@ void SkipMarker(byte[] src) {
9294
AdvanceOffset(length - 2);
9395
}
9496

97+
const int QUANT_DATA_LEN = 65; // 1 byte flags, 64 byte values
9598
void ReadQuantisationTables(byte[] src) {
9699
int offset = AdvanceOffset(2);
97100
int length = MemUtils.ReadU16_BE(src, offset);
@@ -103,8 +106,8 @@ void ReadQuantisationTables(byte[] src) {
103106
// Can have more than one quantisation table
104107
while (length != 0)
105108
{
106-
if (length < 65) Fail("quant table too short: " + length);
107-
length -= 65;
109+
if (length < QUANT_DATA_LEN) Fail("quant table too short: " + length);
110+
length -= QUANT_DATA_LEN;
108111

109112
byte flags = src[offset++];
110113
// As per Table B.4, 16 bit quantisation tables not in baseline JPEG
@@ -198,15 +201,26 @@ void ReadFrameStart(byte[] src, SimpleBitmap bmp) {
198201
offset += 6;
199202

200203
comps = new JpegComponent[numComps];
204+
201205
for (int i = 0; i < numComps; i++)
202206
{
203-
JpegComponent comp = default(JpegComponent);
207+
JpegComponent comp = new JpegComponent();
204208
comp.ID = src[offset++];
205209
byte sampling = src[offset++];
206210
comp.SamplingHor = (byte)(sampling >> 4);
207211
comp.SamplingVer = (byte)(sampling & 0x0F);
208212
comp.QuantTable = src[offset++];
209-
comps[i] = comp;
213+
214+
lowestHor = Math.Max(lowestHor, comp.SamplingHor);
215+
lowestVer = Math.Max(lowestVer, comp.SamplingVer);
216+
comps[i] = comp;
217+
}
218+
219+
// In most JPEG images there is chroma sub-sampling
220+
for (int i = 0; i < numComps; i++)
221+
{
222+
comps[i].BlocksPerMcuX = comps[i].SamplingHor;
223+
comps[i].BlocksPerMcuY = comps[i].SamplingVer;
210224
}
211225
}
212226

@@ -224,13 +238,8 @@ void ReadScanStart(byte[] src) {
224238
SetHuffTables(compID, tables);
225239
}
226240

227-
byte spec_lo = src[offset++];
228-
byte spec_hi = src[offset++];
229-
byte succ_apr = src[offset++];
230-
231-
if (spec_lo != 0) Fail("spectral range start");
232-
if (spec_hi != 0 && spec_hi != 63) Fail("spectral range end");
233-
if (succ_apr != 0) Fail("successive approximation");
241+
// Spectral lo/hi data and successive approximation irrelevant
242+
offset += 3;
234243
}
235244

236245
void SetHuffTables(byte compID, byte tables) {
@@ -258,63 +267,37 @@ void SetHuffTables(byte compID, byte tables) {
258267
53, 60, 61, 54, 47, 55, 62, 63,
259268
};
260269

270+
const int BLOCK_SAMPLES = 8;
261271
void DecodeMCUs(byte[] src, SimpleBitmap bmp) {
262-
int mcus_x = (bmp.Width + 7) / 8;
263-
int mcus_y = (bmp.Height + 7) / 8;
272+
int mcus_x = Utils.CeilDiv(bmp.Width, lowestHor * BLOCK_SAMPLES);
273+
int mcus_y = Utils.CeilDiv(bmp.Height, lowestVer * BLOCK_SAMPLES);
264274

265275
JpegComponent[] comps = this.comps;
266276
int[] block = new int[64];
277+
float[] output = new float[64];
267278

268-
for (int y = 0; y < mcus_y; y++)
269-
for (int x = 0; x < mcus_x; x++)
279+
for (int mcuY = 0; mcuY < mcus_y; mcuY++)
280+
for (int mcuX = 0; mcuX < mcus_x; mcuX++)
270281
{
271282
for (int i = 0; i < comps.Length; i++)
272283
{
273-
// DC value is relative to DC value from prior block
274-
var table = dc_huff_tables[comps[i].DCHuffTable];
275-
int dc_code = ReadHuffman(table, src);
276-
int dc_delta = ReadBiasedValue(src, dc_code);
277-
278-
int dc_value = comps[i].PredDCValue + dc_delta;
279-
comps[i].PredDCValue = dc_value;
280-
281-
byte[] dequant = quant_tables[comps[i].QuantTable];
282-
for (int j = 0; j < block.Length; j++) block[j] = 0;
283-
block[0] = dc_value * dequant[0];
284+
JpegComponent comp = comps[i];
284285

285-
// 63 AC values
286-
table = ac_huff_tables[comps[i].ACHuffTable];
287-
int idx = 1;
288-
do {
289-
int code = ReadHuffman(table, src);
290-
if (code == 0) break;
291-
292-
int bits = code & 0x0F;
293-
int num_zeros = code >> 4;
294-
295-
if (bits == 0) {
296-
if (code == 0) break; // 0 value - end of block
297-
// TODO is this right?
298-
if (num_zeros != 15) Fail("too many zeroes");
299-
idx += 16;
300-
} else {
301-
idx += num_zeros;
302-
int lin = zigzag_to_linear[idx];
303-
block[lin] = ReadBiasedValue(src, bits) * dequant[idx];
304-
idx++;
305-
}
306-
} while (idx < 64);
307-
308-
float[] output = new float[64];
309-
IDCT(block, output);
286+
for (int blockY = 0; blockY < comp.BlocksPerMcuY; blockY++)
287+
for (int blockX = 0; blockX < comp.BlocksPerMcuX; blockX++)
288+
{
289+
DecodeBlock(comp, src, block, output);
290+
IDCT(block, output);
291+
}
310292

311-
for (int YY = 0; YY < 8; YY++)
312-
for (int XX = 0; XX < 8; XX++)
293+
for (int YY = 0; YY < BLOCK_SAMPLES; YY++)
294+
for (int XX = 0; XX < BLOCK_SAMPLES; XX++)
313295
{
314-
int globalX = x * 8 + XX;
315-
int globalY = y * 8 + YY;
296+
int globalX = mcuX * BLOCK_SAMPLES + XX;
297+
int globalY = mcuY * BLOCK_SAMPLES + YY;
298+
316299
if (globalX < bmp.Width && globalY < bmp.Height) {
317-
byte rgb = (byte)output[YY*8+XX];
300+
byte rgb = (byte)output[YY*BLOCK_SAMPLES+XX];
318301
Pixel p = new Pixel(rgb, rgb, rgb, 255);
319302
bmp.pixels[globalY * bmp.Width + globalX] = p;
320303
}
@@ -325,6 +308,46 @@ void DecodeMCUs(byte[] src, SimpleBitmap bmp) {
325308
// Fail("MCUs");
326309
}
327310

311+
void DecodeBlock(JpegComponent comp, byte[] src, int[] block, float[] output) {
312+
// DC value is relative to DC value from prior block
313+
var table = dc_huff_tables[comp.DCHuffTable];
314+
int dc_code = ReadHuffman(table, src);
315+
int dc_delta = ReadBiasedValue(src, dc_code);
316+
317+
int dc_value = comp.PredDCValue + dc_delta;
318+
comp.PredDCValue = dc_value;
319+
320+
byte[] dequant = quant_tables[comp.QuantTable];
321+
for (int j = 0; j < block.Length; j++) block[j] = 0;
322+
block[0] = dc_value * dequant[0];
323+
324+
// 63 AC values
325+
table = ac_huff_tables[comp.ACHuffTable];
326+
int idx = 1;
327+
328+
do {
329+
int code = ReadHuffman(table, src);
330+
if (code == 0) break;
331+
332+
int bits = code & 0x0F;
333+
int num_zeros = code >> 4;
334+
335+
if (bits == 0) {
336+
if (code == 0) break; // 0 value - end of block
337+
// TODO is this right?
338+
if (num_zeros != 15) Fail("too many zeroes");
339+
idx += 16;
340+
} else {
341+
idx += num_zeros;
342+
int lin = zigzag_to_linear[idx];
343+
block[lin] = ReadBiasedValue(src, bits) * dequant[idx];
344+
idx++;
345+
}
346+
} while (idx < 64);
347+
348+
IDCT(block, output);
349+
}
350+
328351
void IDCT(int[] block, float[] output) {
329352
for (int y = 0; y < 8; y++)
330353
for (int x = 0; x < 8; x++)
@@ -346,6 +369,10 @@ void IDCT(int[] block, float[] output) {
346369
}
347370
}
348371

372+
void OutputImage(SimpleBitmap bmp) {
373+
374+
}
375+
349376
uint bit_buf;
350377
int bit_cnt;
351378
bool end;
@@ -409,12 +436,16 @@ int ReadBiasedValue(byte[] src, int bits) {
409436
}
410437
}
411438

412-
struct JpegComponent
439+
class JpegComponent
413440
{
414441
public byte ID;
442+
public byte QuantTable;
443+
415444
public byte SamplingHor;
416445
public byte SamplingVer;
417-
public byte QuantTable;
446+
public byte BlocksPerMcuX;
447+
public byte BlocksPerMcuY;
448+
418449
public byte ACHuffTable;
419450
public byte DCHuffTable;
420451
public int PredDCValue;

MCGalaxy/util/SparseBitSet.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public sealed class SparseBitSet {
2828

2929
/// <summary> Initialises a sparse bit set for the given 3D volume. </summary>
3030
public SparseBitSet(int width, int height, int length) {
31-
chunksX = Utils.CeilDiv16(width);
32-
chunksY = Utils.CeilDiv16(height);
33-
chunksZ = Utils.CeilDiv16(length);
31+
chunksX = Utils.CeilDiv(width, 16);
32+
chunksY = Utils.CeilDiv(height, 16);
33+
chunksZ = Utils.CeilDiv(length, 16);
3434
bits = new byte[chunksX * chunksY * chunksZ][];
3535
}
3636

MCGalaxy/util/Utils.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ public static int Clamp(int value, int lo, int hi) {
5151
return Math.Max(Math.Min(value, hi), lo);
5252
}
5353

54-
/// <summary> Divides by 16, rounding up if there is a remainder. </summary>
55-
public static int CeilDiv16(int x) { return (x + 15) / 16; }
54+
/// <summary> Divides x by y, rounding up if there is a remainder. </summary>
55+
public static int CeilDiv(int x, int y) {
56+
return (x + (y - 1)) / y;
57+
}
5658

5759

5860
public static List<string> ReadAllLinesList(string path) {

0 commit comments

Comments
 (0)