1 /++
2 	Basic .bmp file format implementation for [arsd.color.MemoryImage].
3 	Compare with [arsd.png] basic functionality.
4 +/
5 module arsd.bmp;
6 
7 import arsd.color;
8 
9 //version = arsd_debug_bitmap_loader;
10 
11 
12 /// Reads a .bmp file from the given `filename`
13 MemoryImage readBmp(string filename) {
14 	import core.stdc.stdio;
15 
16 	FILE* fp = fopen((filename ~ "\0").ptr, "rb".ptr);
17 	if(fp is null)
18 		throw new Exception("can't open save file");
19 	scope(exit) fclose(fp);
20 
21 	void specialFread(void* tgt, size_t size) {
22 		version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("ofs: 0x%08x\n", cast(uint)ftell(fp)); }
23 		fread(tgt, size, 1, fp);
24 	}
25 
26 	return readBmpIndirect(&specialFread);
27 }
28 
29 /// Reads a bitmap out of an in-memory array of data. For example, that returned from [std.file.read].
30 MemoryImage readBmp(in ubyte[] data) {
31 	const(ubyte)[] current = data;
32 	void specialFread(void* tgt, size_t size) {
33 		while(size) {
34 			if (current.length == 0) throw new Exception("out of bmp data"); // it's not *that* fatal, so don't throw RangeError
35 			*cast(ubyte*)(tgt) = current[0];
36 			current = current[1 .. $];
37 			tgt++;
38 			size--;
39 		}
40 	}
41 
42 	return readBmpIndirect(&specialFread);
43 }
44 
45 /// Reads using a delegate to read instead of assuming a direct file
46 MemoryImage readBmpIndirect(scope void delegate(void*, size_t) fread) {
47 	uint read4()  { uint what; fread(&what, 4); return what; }
48 	ushort read2(){ ushort what; fread(&what, 2); return what; }
49 	ubyte read1(){ ubyte what; fread(&what, 1); return what; }
50 
51 	void require1(ubyte t, size_t line = __LINE__) {
52 		if(read1() != t)
53 			throw new Exception("didn't get expected byte value", __FILE__, line);
54 	}
55 	void require2(ushort t) {
56 		if(read2() != t)
57 			throw new Exception("didn't get expected short value");
58 	}
59 	void require4(uint t, size_t line = __LINE__) {
60 		auto got = read4();
61 		//import std.conv;
62 		if(got != t)
63 			throw new Exception("didn't get expected int value " /*~ to!string(got)*/, __FILE__, line);
64 	}
65 
66 	require1('B');
67 	require1('M');
68 
69 	auto fileSize = read4(); // size of file in bytes
70 	require2(0); // reserved
71 	require2(0); 	// reserved
72 
73 	auto offsetToBits = read4();
74 	version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("pixel data offset: 0x%08x\n", cast(uint)offsetToBits); }
75 
76 	auto sizeOfBitmapInfoHeader = read4();
77 	if (sizeOfBitmapInfoHeader < 12) throw new Exception("invalid bitmap header size");
78 
79 	int width, height, rdheight;
80 
81 	if (sizeOfBitmapInfoHeader == 12) {
82 		width = read2();
83 		rdheight = cast(short)read2();
84 	} else {
85 		if (sizeOfBitmapInfoHeader < 16) throw new Exception("invalid bitmap header size");
86 		sizeOfBitmapInfoHeader -= 4; // hack!
87 		width = read4();
88 		rdheight = cast(int)read4();
89 	}
90 
91 	height = (rdheight < 0 ? -rdheight : rdheight);
92 	rdheight = (rdheight < 0 ? 1 : -1); // so we can use it as delta (note the inverted sign)
93 
94 	if (width < 1 || height < 1) throw new Exception("invalid bitmap dimensions");
95 
96 	require2(1); // planes
97 
98 	auto bitsPerPixel = read2();
99 	switch (bitsPerPixel) {
100 		case 1: case 2: case 4: case 8: case 16: case 24: case 32: break;
101 		default: throw new Exception("invalid bitmap depth");
102 	}
103 
104 	/*
105 		0 = BI_RGB
106 		1 = BI_RLE8   RLE 8-bit/pixel   Can be used only with 8-bit/pixel bitmaps
107 		2 = BI_RLE4   RLE 4-bit/pixel   Can be used only with 4-bit/pixel bitmaps
108 		3 = BI_BITFIELDS
109 	*/
110 	uint compression = 0;
111 	uint sizeOfUncompressedData = 0;
112 	uint xPixelsPerMeter = 0;
113 	uint yPixelsPerMeter = 0;
114 	uint colorsUsed = 0;
115 	uint colorsImportant = 0;
116 
117 	sizeOfBitmapInfoHeader -= 12;
118 	if (sizeOfBitmapInfoHeader > 0) {
119 		if (sizeOfBitmapInfoHeader < 6*4) throw new Exception("invalid bitmap header size");
120 		sizeOfBitmapInfoHeader -= 6*4;
121 		compression = read4();
122 		sizeOfUncompressedData = read4();
123 		xPixelsPerMeter = read4();
124 		yPixelsPerMeter = read4();
125 		colorsUsed = read4();
126 		colorsImportant = read4();
127 	}
128 
129 	if (compression > 3) throw new Exception("invalid bitmap compression");
130 	if (compression == 1 && bitsPerPixel != 8) throw new Exception("invalid bitmap compression");
131 	if (compression == 2 && bitsPerPixel != 4) throw new Exception("invalid bitmap compression");
132 
133 	version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("compression: %u; bpp: %u\n", compression, cast(uint)bitsPerPixel); }
134 
135 	uint redMask;
136 	uint greenMask;
137 	uint blueMask;
138 	uint alphaMask;
139 	if (compression == 3) {
140 		if (sizeOfBitmapInfoHeader < 4*4) throw new Exception("invalid bitmap compression");
141 		sizeOfBitmapInfoHeader -= 4*4;
142 		redMask = read4();
143 		greenMask = read4();
144 		blueMask = read4();
145 		alphaMask = read4();
146 	}
147 	// FIXME: we could probably handle RLE4 as well
148 
149 	// I don't know about the rest of the header, so I'm just skipping it.
150 	version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("header bytes left: %u\n", cast(uint)sizeOfBitmapInfoHeader); }
151 	foreach (skip; 0..sizeOfBitmapInfoHeader) read1();
152 
153 	if(bitsPerPixel <= 8) {
154 		// indexed image
155 		version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("colorsUsed=%u; colorsImportant=%u\n", colorsUsed, colorsImportant); }
156 		if (colorsUsed == 0 || colorsUsed > (1 << bitsPerPixel)) colorsUsed = (1 << bitsPerPixel);
157 		auto img = new IndexedImage(width, height);
158 		img.palette.reserve(1 << bitsPerPixel);
159 		foreach(idx; 0 .. /*(1 << bitsPerPixel)*/colorsUsed) {
160 			auto b = read1();
161 			auto g = read1();
162 			auto r = read1();
163 			auto reserved = read1();
164 
165 			img.palette ~= Color(r, g, b);
166 		}
167 		while (img.palette.length < (1 << bitsPerPixel)) img.palette ~= Color.transparent;
168 
169 		// and the data
170 		int bytesPerPixel = 1;
171 		auto offsetStart = (rdheight > 0 ? 0 : width * height * bytesPerPixel);
172 		int bytesRead = 0;
173 
174 		if (compression == 1) {
175 			// this is complicated
176 			assert(bitsPerPixel == 8); // always
177 			int x = 0, y = (rdheight > 0 ? 0 : height-1);
178 			void setpix (int v) {
179 				if (x >= 0 && y >= 0 && x < width && y < height) img.data.ptr[y*width+x] = v&0xff;
180 				++x;
181 			}
182 			version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("width=%d; height=%d; rdheight=%d\n", width, height, rdheight); }
183 			for (;;) {
184 				ubyte codelen = read1();
185 				ubyte codecode = read1();
186 				version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("x=%d; y=%d; len=%u; code=%u\n", x, y, cast(uint)codelen, cast(uint)codecode); }
187 				bytesRead += 2;
188 				if (codelen == 0) {
189 					// special code
190 					if (codecode == 0) {
191 						// end of line
192 						version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("  EOL\n"); }
193 						while (x < width) setpix(1);
194 						x = 0;
195 						y += rdheight;
196 						if (y < 0 || y >= height) break; // ooops
197 					} else if (codecode == 1) {
198 						version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("  EOB\n"); }
199 						// end of bitmap
200 						break;
201 					} else if (codecode == 2) {
202 						// delta
203 						int xofs = read1();
204 						int yofs = read1();
205 						version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("  deltax=%d; deltay=%d\n", xofs, yofs); }
206 						bytesRead += 2;
207 						x += xofs;
208 						y += yofs*rdheight;
209 						if (y < 0 || y >= height) break; // ooops
210 					} else {
211 						version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("  LITERAL: %u\n", cast(uint)codecode); }
212 						// literal copy
213 						while (codecode-- > 0) {
214 							setpix(read1());
215 							++bytesRead;
216 						}
217 						version(arsd_debug_bitmap_loader) if (bytesRead%2) { import core.stdc.stdio; printf("  LITERAL SKIP\n"); }
218 						if (bytesRead%2) { read1(); ++bytesRead; }
219 						assert(bytesRead%2 == 0);
220 					}
221 				} else {
222 					while (codelen-- > 0) setpix(codecode);
223 				}
224 			}
225 		} else if (compression == 2) {
226 			throw new Exception("4RLE for bitmaps aren't supported yet");
227 		} else {
228 			for(int y = height; y > 0; y--) {
229 				if (rdheight < 0) offsetStart -= width * bytesPerPixel;
230 				int offset = offsetStart;
231 				while (bytesRead%4 != 0) {
232 					read1();
233 					++bytesRead;
234 				}
235 				bytesRead = 0;
236 				for(int x = 0; x < width; x++) {
237 					auto b = read1();
238 					++bytesRead;
239 					if(bitsPerPixel == 8) {
240 						img.data[offset++] = b;
241 					} else if(bitsPerPixel == 4) {
242 						img.data[offset++] = (b&0xf0) >> 4;
243 						x++;
244 						if(offset == img.data.length)
245 							break;
246 						img.data[offset++] = (b&0x0f);
247 					} else if(bitsPerPixel == 2) {
248 						img.data[offset++] = (b & 0b11000000) >> 6;
249 						x++;
250 						if(offset == img.data.length)
251 							break;
252 						img.data[offset++] = (b & 0b00110000) >> 4;
253 						x++;
254 						if(offset == img.data.length)
255 							break;
256 						img.data[offset++] = (b & 0b00001100) >> 2;
257 						x++;
258 						if(offset == img.data.length)
259 							break;
260 						img.data[offset++] = (b & 0b00000011) >> 0;
261 					} else if(bitsPerPixel == 1) {
262 						foreach(lol; 0 .. 8) {
263 							img.data[offset++] = (b & (1 << lol)) >> (7 - lol);
264 							x++;
265 							if(offset == img.data.length)
266 								break;
267 						}
268 						x--; // we do this once too many times in the loop
269 					} else assert(0);
270 					// I don't think these happen in the wild but I could be wrong, my bmp knowledge is somewhat outdated
271 				}
272 				if (rdheight > 0) offsetStart += width * bytesPerPixel;
273 			}
274 		}
275 
276 		return img;
277 	} else {
278 		if (compression != 0) throw new Exception("invalid bitmap compression");
279 		// true color image
280 		auto img = new TrueColorImage(width, height);
281 
282 		// no palette, so straight into the data
283 		int offsetStart = width * height * 4;
284 		int bytesPerPixel = 4;
285 		for(int y = height; y > 0; y--) {
286 			offsetStart -= width * bytesPerPixel;
287 			int offset = offsetStart;
288 			int b = 0;
289 			foreach(x; 0 .. width) {
290 				if(compression == 3) {
291 					ubyte[8] buffer;
292 					assert(bitsPerPixel / 8 < 8);
293 					foreach(lol; 0 .. bitsPerPixel / 8) {
294 						if(lol >= buffer.length)
295 							throw new Exception("wtf");
296 						buffer[lol] = read1();
297 						b++;
298 					}
299 
300 					ulong data = *(cast(ulong*) buffer.ptr);
301 
302 					auto blue = data & blueMask;
303 					auto green = data & greenMask;
304 					auto red = data & redMask;
305 					auto alpha = data & alphaMask;
306 
307 					if(blueMask)
308 						blue = blue * 255 / blueMask;
309 					if(greenMask)
310 						green = green * 255 / greenMask;
311 					if(redMask)
312 						red = red * 255 / redMask;
313 					if(alphaMask)
314 						alpha = alpha * 255 / alphaMask;
315 					else
316 						alpha = 255;
317 
318 					img.imageData.bytes[offset + 2] = cast(ubyte) blue;
319 					img.imageData.bytes[offset + 1] = cast(ubyte) green;
320 					img.imageData.bytes[offset + 0] = cast(ubyte) red;
321 					img.imageData.bytes[offset + 3] = cast(ubyte) alpha;
322 				} else {
323 					assert(compression == 0);
324 
325 					if(bitsPerPixel == 24 || bitsPerPixel == 32) {
326 						img.imageData.bytes[offset + 2] = read1(); // b
327 						img.imageData.bytes[offset + 1] = read1(); // g
328 						img.imageData.bytes[offset + 0] = read1(); // r
329 						if(bitsPerPixel == 32) {
330 							img.imageData.bytes[offset + 3] = read1(); // a
331 							b++;
332 						} else {
333 							img.imageData.bytes[offset + 3] = 255; // a
334 						}
335 						b += 3;
336 					} else {
337 						assert(bitsPerPixel == 16);
338 						// these are stored xrrrrrgggggbbbbb
339 						ushort d = read1();
340 						d |= cast(ushort)read1() << 8;
341 							// we expect 8 bit numbers but these only give 5 bits of info,
342 							// therefore we shift left 3 to get the right stuff.
343 						img.imageData.bytes[offset + 0] = (d & 0b0111110000000000) >> (10-3);
344 						img.imageData.bytes[offset + 1] = (d & 0b0000001111100000) >> (5-3);
345 						img.imageData.bytes[offset + 2] = (d & 0b0000000000011111) << 3;
346 						img.imageData.bytes[offset + 3] = 255; // r
347 						b += 2;
348 					}
349 				}
350 
351 				offset += bytesPerPixel;
352 			}
353 
354 			int w = b%4;
355 			if(w)
356 			for(int a = 0; a < 4-w; a++)
357 				read1(); // pad until divisible by four
358 		}
359 
360 
361 		return img;
362 	}
363 
364 	assert(0);
365 }
366 
367 /// Writes the `img` out to `filename`, in .bmp format. Writes [TrueColorImage] out
368 /// as a 24 bmp and [IndexedImage] out as an 8 bit bmp. Drops transparency information.
369 void writeBmp(MemoryImage img, string filename) {
370 	import core.stdc.stdio;
371 	FILE* fp = fopen((filename ~ "\0").ptr, "wb".ptr);
372 	if(fp is null)
373 		throw new Exception("can't open save file");
374 	scope(exit) fclose(fp);
375 
376 	void write4(uint what)  { fwrite(&what, 4, 1, fp); }
377 	void write2(ushort what){ fwrite(&what, 2, 1, fp); }
378 	void write1(ubyte what) { fputc(what, fp); }
379 
380 	int width = img.width;
381 	int height = img.height;
382 	ushort bitsPerPixel;
383 
384 	ubyte[] data;
385 	Color[] palette;
386 
387 	// FIXME we should be able to write RGBA bitmaps too, though it seems like not many
388 	// programs correctly read them!
389 
390 	if(auto tci = cast(TrueColorImage) img) {
391 		bitsPerPixel = 24;
392 		data = tci.imageData.bytes;
393 		// we could also realistically do 16 but meh
394 	} else if(auto pi = cast(IndexedImage) img) {
395 		// FIXME: implement other bpps for more efficiency
396 		/*
397 		if(pi.palette.length == 2)
398 			bitsPerPixel = 1;
399 		else if(pi.palette.length <= 16)
400 			bitsPerPixel = 4;
401 		else
402 		*/
403 			bitsPerPixel = 8;
404 		data = pi.data;
405 		palette = pi.palette;
406 	} else throw new Exception("I can't save this image type " ~ img.classinfo.name);
407 
408 	ushort offsetToBits;
409 	if(bitsPerPixel == 8)
410 		offsetToBits = 1078;
411 	if (bitsPerPixel == 24 || bitsPerPixel == 16)
412 		offsetToBits = 54;
413 	else
414 		offsetToBits = cast(ushort)(54 + 4 * 1 << bitsPerPixel); // room for the palette...
415 
416 	uint fileSize = offsetToBits;
417 	if(bitsPerPixel == 8)
418 		fileSize += height * (width + width%4);
419 	else if(bitsPerPixel == 24)
420 		fileSize += height * ((width * 3) + (!((width*3)%4) ? 0 : 4-((width*3)%4)));
421 	else assert(0, "not implemented"); // FIXME
422 
423 	write1('B');
424 	write1('M');
425 
426 	write4(fileSize); // size of file in bytes
427 	write2(0); 	// reserved
428 	write2(0); 	// reserved
429 	write4(offsetToBits); // offset to the bitmap data
430 
431 	write4(40); // size of BITMAPINFOHEADER
432 
433 	write4(width); // width
434 	write4(height); // height
435 
436 	write2(1); // planes
437 	write2(bitsPerPixel); // bpp
438 	write4(0); // compression
439 	write4(0); // size of uncompressed
440 	write4(0); // x pels per meter
441 	write4(0); // y pels per meter
442 	write4(0); // colors used
443 	write4(0); // colors important
444 
445 	// And here we write the palette
446 	if(bitsPerPixel <= 8)
447 		foreach(c; palette[0..(1 << bitsPerPixel)]){
448 			write1(c.b);
449 			write1(c.g);
450 			write1(c.r);
451 			write1(0);
452 		}
453 
454 	// And finally the data
455 
456 	int bytesPerPixel;
457 	if(bitsPerPixel == 8)
458 		bytesPerPixel = 1;
459 	else if(bitsPerPixel == 24)
460 		bytesPerPixel = 4;
461 	else assert(0, "not implemented"); // FIXME
462 
463 	int offsetStart = cast(int) data.length;
464 	for(int y = height; y > 0; y--) {
465 		offsetStart -= width * bytesPerPixel;
466 		int offset = offsetStart;
467 		int b = 0;
468 		foreach(x; 0 .. width) {
469 			if(bitsPerPixel == 8) {
470 				write1(data[offset]);
471 				b++;
472 			} else if(bitsPerPixel == 24) {
473 				write1(data[offset + 2]); // blue
474 				write1(data[offset + 1]); // green
475 				write1(data[offset + 0]); // red
476 				b += 3;
477 			} else assert(0); // FIXME
478 			offset += bytesPerPixel;
479 		}
480 
481 		int w = b%4;
482 		if(w)
483 		for(int a = 0; a < 4-w; a++)
484 			write1(0); // pad until divisible by four
485 	}
486 }
487 
488 /+
489 void main() {
490 	import arsd.simpledisplay;
491 	//import std.file;
492 	//auto img = readBmp(cast(ubyte[]) std.file.read("/home/me/test2.bmp"));
493 	auto img = readBmp("/home/me/test2.bmp");
494 	import std.stdio;
495 	writeln((cast(Object)img).toString());
496 	displayImage(Image.fromMemoryImage(img));
497 	//img.writeBmp("/home/me/test2.bmp");
498 }
499 +/