1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2006 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24/*
25   Code to load and save surfaces in Windows BMP format.
26
27   Why support BMP format?  Well, it's a native format for Windows, and
28   most image processing programs can read and write it.  It would be nice
29   to be able to have at least one image format that we can natively load
30   and save, and since PNG is so complex that it would bloat the library,
31   BMP is a good alternative.
32
33   This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
34*/
35
36#include "SDL_video.h"
37#include "SDL_endian.h"
38
39/* Compression encodings for BMP files */
40#ifndef BI_RGB
41#define BI_RGB		0
42#define BI_RLE8		1
43#define BI_RLE4		2
44#define BI_BITFIELDS	3
45#endif
46
47
48SDL_Surface * SDL_LoadBMP_RW (SDL_RWops *src, int freesrc)
49{
50	int was_error;
51	long fp_offset;
52	int bmpPitch;
53	int i, pad;
54	SDL_Surface *surface;
55	Uint32 Rmask;
56	Uint32 Gmask;
57	Uint32 Bmask;
58	SDL_Palette *palette;
59	Uint8 *bits;
60	int ExpandBMP;
61
62	/* The Win32 BMP file header (14 bytes) */
63	char   magic[2];
64	Uint32 bfSize;
65	Uint16 bfReserved1;
66	Uint16 bfReserved2;
67	Uint32 bfOffBits;
68
69	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
70	Uint32 biSize;
71	Sint32 biWidth;
72	Sint32 biHeight;
73	Uint16 biPlanes;
74	Uint16 biBitCount;
75	Uint32 biCompression;
76	Uint32 biSizeImage;
77	Sint32 biXPelsPerMeter;
78	Sint32 biYPelsPerMeter;
79	Uint32 biClrUsed;
80	Uint32 biClrImportant;
81
82	/* Make sure we are passed a valid data source */
83	surface = NULL;
84	was_error = 0;
85	if ( src == NULL ) {
86		was_error = 1;
87		goto done;
88	}
89
90	/* Read in the BMP file header */
91	fp_offset = SDL_RWtell(src);
92	SDL_ClearError();
93	if ( SDL_RWread(src, magic, 1, 2) != 2 ) {
94		SDL_Error(SDL_EFREAD);
95		was_error = 1;
96		goto done;
97	}
98	if ( SDL_strncmp(magic, "BM", 2) != 0 ) {
99		SDL_SetError("File is not a Windows BMP file");
100		was_error = 1;
101		goto done;
102	}
103	bfSize		= SDL_ReadLE32(src);
104	bfReserved1	= SDL_ReadLE16(src);
105	bfReserved2	= SDL_ReadLE16(src);
106	bfOffBits	= SDL_ReadLE32(src);
107
108	/* Read the Win32 BITMAPINFOHEADER */
109	biSize		= SDL_ReadLE32(src);
110	if ( biSize == 12 ) {
111		biWidth		= (Uint32)SDL_ReadLE16(src);
112		biHeight	= (Uint32)SDL_ReadLE16(src);
113		biPlanes	= SDL_ReadLE16(src);
114		biBitCount	= SDL_ReadLE16(src);
115		biCompression	= BI_RGB;
116		biSizeImage	= 0;
117		biXPelsPerMeter	= 0;
118		biYPelsPerMeter	= 0;
119		biClrUsed	= 0;
120		biClrImportant	= 0;
121	} else {
122		biWidth		= SDL_ReadLE32(src);
123		biHeight	= SDL_ReadLE32(src);
124		biPlanes	= SDL_ReadLE16(src);
125		biBitCount	= SDL_ReadLE16(src);
126		biCompression	= SDL_ReadLE32(src);
127		biSizeImage	= SDL_ReadLE32(src);
128		biXPelsPerMeter	= SDL_ReadLE32(src);
129		biYPelsPerMeter	= SDL_ReadLE32(src);
130		biClrUsed	= SDL_ReadLE32(src);
131		biClrImportant	= SDL_ReadLE32(src);
132	}
133
134	/* Check for read error */
135	if ( SDL_strcmp(SDL_GetError(), "") != 0 ) {
136		was_error = 1;
137		goto done;
138	}
139
140	/* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
141	switch (biBitCount) {
142		case 1:
143		case 4:
144			ExpandBMP = biBitCount;
145			biBitCount = 8;
146			break;
147		default:
148			ExpandBMP = 0;
149			break;
150	}
151
152	/* We don't support any BMP compression right now */
153	Rmask = Gmask = Bmask = 0;
154	switch (biCompression) {
155		case BI_RGB:
156			/* If there are no masks, use the defaults */
157			if ( bfOffBits == (14+biSize) ) {
158				/* Default values for the BMP format */
159				switch (biBitCount) {
160					case 15:
161					case 16:
162						Rmask = 0x7C00;
163						Gmask = 0x03E0;
164						Bmask = 0x001F;
165						break;
166					case 24:
167#if SDL_BYTEORDER == SDL_BIG_ENDIAN
168					        Rmask = 0x000000FF;
169					        Gmask = 0x0000FF00;
170					        Bmask = 0x00FF0000;
171						break;
172#endif
173					case 32:
174						Rmask = 0x00FF0000;
175						Gmask = 0x0000FF00;
176						Bmask = 0x000000FF;
177						break;
178					default:
179						break;
180				}
181				break;
182			}
183			/* Fall through -- read the RGB masks */
184
185		case BI_BITFIELDS:
186			switch (biBitCount) {
187				case 15:
188				case 16:
189				case 32:
190					Rmask = SDL_ReadLE32(src);
191					Gmask = SDL_ReadLE32(src);
192					Bmask = SDL_ReadLE32(src);
193					break;
194				default:
195					break;
196			}
197			break;
198		default:
199			SDL_SetError("Compressed BMP files not supported");
200			was_error = 1;
201			goto done;
202	}
203
204	/* Create a compatible surface, note that the colors are RGB ordered */
205	surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
206			biWidth, biHeight, biBitCount, Rmask, Gmask, Bmask, 0);
207	if ( surface == NULL ) {
208		was_error = 1;
209		goto done;
210	}
211
212	/* Load the palette, if any */
213	palette = (surface->format)->palette;
214	if ( palette ) {
215		if ( biClrUsed == 0 ) {
216			biClrUsed = 1 << biBitCount;
217		}
218		if ( biSize == 12 ) {
219			for ( i = 0; i < (int)biClrUsed; ++i ) {
220				SDL_RWread(src, &palette->colors[i].b, 1, 1);
221				SDL_RWread(src, &palette->colors[i].g, 1, 1);
222				SDL_RWread(src, &palette->colors[i].r, 1, 1);
223				palette->colors[i].unused = 0;
224			}
225		} else {
226			for ( i = 0; i < (int)biClrUsed; ++i ) {
227				SDL_RWread(src, &palette->colors[i].b, 1, 1);
228				SDL_RWread(src, &palette->colors[i].g, 1, 1);
229				SDL_RWread(src, &palette->colors[i].r, 1, 1);
230				SDL_RWread(src, &palette->colors[i].unused, 1, 1);
231			}
232		}
233		palette->ncolors = biClrUsed;
234	}
235
236	/* Read the surface pixels.  Note that the bmp image is upside down */
237	if ( SDL_RWseek(src, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
238		SDL_Error(SDL_EFSEEK);
239		was_error = 1;
240		goto done;
241	}
242	bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
243	switch (ExpandBMP) {
244		case 1:
245			bmpPitch = (biWidth + 7) >> 3;
246			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
247			break;
248		case 4:
249			bmpPitch = (biWidth + 1) >> 1;
250			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
251			break;
252		default:
253			pad  = ((surface->pitch%4) ?
254					(4-(surface->pitch%4)) : 0);
255			break;
256	}
257	while ( bits > (Uint8 *)surface->pixels ) {
258		bits -= surface->pitch;
259		switch (ExpandBMP) {
260			case 1:
261			case 4: {
262			Uint8 pixel = 0;
263			int   shift = (8-ExpandBMP);
264			for ( i=0; i<surface->w; ++i ) {
265				if ( i%(8/ExpandBMP) == 0 ) {
266					if ( !SDL_RWread(src, &pixel, 1, 1) ) {
267						SDL_SetError(
268					"Error reading from BMP");
269						was_error = 1;
270						goto done;
271					}
272				}
273				*(bits+i) = (pixel>>shift);
274				pixel <<= ExpandBMP;
275			} }
276			break;
277
278			default:
279			if ( SDL_RWread(src, bits, 1, surface->pitch)
280							 != surface->pitch ) {
281				SDL_Error(SDL_EFREAD);
282				was_error = 1;
283				goto done;
284			}
285#if SDL_BYTEORDER == SDL_BIG_ENDIAN
286			/* Byte-swap the pixels if needed. Note that the 24bpp
287			   case has already been taken care of above. */
288			switch(biBitCount) {
289				case 15:
290				case 16: {
291				        Uint16 *pix = (Uint16 *)bits;
292					for(i = 0; i < surface->w; i++)
293					        pix[i] = SDL_Swap16(pix[i]);
294					break;
295				}
296
297				case 32: {
298				        Uint32 *pix = (Uint32 *)bits;
299					for(i = 0; i < surface->w; i++)
300					        pix[i] = SDL_Swap32(pix[i]);
301					break;
302				}
303			}
304#endif
305			break;
306		}
307		/* Skip padding bytes, ugh */
308		if ( pad ) {
309			Uint8 padbyte;
310			for ( i=0; i<pad; ++i ) {
311				SDL_RWread(src, &padbyte, 1, 1);
312			}
313		}
314	}
315done:
316	if ( was_error ) {
317		if ( src ) {
318			SDL_RWseek(src, fp_offset, RW_SEEK_SET);
319		}
320		if ( surface ) {
321			SDL_FreeSurface(surface);
322		}
323		surface = NULL;
324	}
325	if ( freesrc && src ) {
326		SDL_RWclose(src);
327	}
328	return(surface);
329}
330
331int SDL_SaveBMP_RW (SDL_Surface *saveme, SDL_RWops *dst, int freedst)
332{
333	long fp_offset;
334	int i, pad;
335	SDL_Surface *surface;
336	Uint8 *bits;
337
338	/* The Win32 BMP file header (14 bytes) */
339	char   magic[2] = { 'B', 'M' };
340	Uint32 bfSize;
341	Uint16 bfReserved1;
342	Uint16 bfReserved2;
343	Uint32 bfOffBits;
344
345	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
346	Uint32 biSize;
347	Sint32 biWidth;
348	Sint32 biHeight;
349	Uint16 biPlanes;
350	Uint16 biBitCount;
351	Uint32 biCompression;
352	Uint32 biSizeImage;
353	Sint32 biXPelsPerMeter;
354	Sint32 biYPelsPerMeter;
355	Uint32 biClrUsed;
356	Uint32 biClrImportant;
357
358	/* Make sure we have somewhere to save */
359	surface = NULL;
360	if ( dst ) {
361		if ( saveme->format->palette ) {
362			if ( saveme->format->BitsPerPixel == 8 ) {
363				surface = saveme;
364			} else {
365				SDL_SetError("%d bpp BMP files not supported",
366						saveme->format->BitsPerPixel);
367			}
368		}
369		else if ( (saveme->format->BitsPerPixel == 24) &&
370#if SDL_BYTEORDER == SDL_LIL_ENDIAN
371				(saveme->format->Rmask == 0x00FF0000) &&
372				(saveme->format->Gmask == 0x0000FF00) &&
373				(saveme->format->Bmask == 0x000000FF)
374#else
375				(saveme->format->Rmask == 0x000000FF) &&
376				(saveme->format->Gmask == 0x0000FF00) &&
377				(saveme->format->Bmask == 0x00FF0000)
378#endif
379			  ) {
380			surface = saveme;
381		} else {
382			SDL_Rect bounds;
383
384			/* Convert to 24 bits per pixel */
385			surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
386					saveme->w, saveme->h, 24,
387#if SDL_BYTEORDER == SDL_LIL_ENDIAN
388					0x00FF0000, 0x0000FF00, 0x000000FF,
389#else
390					0x000000FF, 0x0000FF00, 0x00FF0000,
391#endif
392					0);
393			if ( surface != NULL ) {
394				bounds.x = 0;
395				bounds.y = 0;
396				bounds.w = saveme->w;
397				bounds.h = saveme->h;
398				if ( SDL_LowerBlit(saveme, &bounds, surface,
399							&bounds) < 0 ) {
400					SDL_FreeSurface(surface);
401					SDL_SetError(
402					"Couldn't convert image to 24 bpp");
403					surface = NULL;
404				}
405			}
406		}
407	}
408
409	if ( surface && (SDL_LockSurface(surface) == 0) ) {
410		const int bw = surface->w*surface->format->BytesPerPixel;
411
412		/* Set the BMP file header values */
413		bfSize = 0;		 /* We'll write this when we're done */
414		bfReserved1 = 0;
415		bfReserved2 = 0;
416		bfOffBits = 0;		/* We'll write this when we're done */
417
418		/* Write the BMP file header values */
419		fp_offset = SDL_RWtell(dst);
420		SDL_ClearError();
421		SDL_RWwrite(dst, magic, 2, 1);
422		SDL_WriteLE32(dst, bfSize);
423		SDL_WriteLE16(dst, bfReserved1);
424		SDL_WriteLE16(dst, bfReserved2);
425		SDL_WriteLE32(dst, bfOffBits);
426
427		/* Set the BMP info values */
428		biSize = 40;
429		biWidth = surface->w;
430		biHeight = surface->h;
431		biPlanes = 1;
432		biBitCount = surface->format->BitsPerPixel;
433		biCompression = BI_RGB;
434		biSizeImage = surface->h*surface->pitch;
435		biXPelsPerMeter = 0;
436		biYPelsPerMeter = 0;
437		if ( surface->format->palette ) {
438			biClrUsed = surface->format->palette->ncolors;
439		} else {
440			biClrUsed = 0;
441		}
442		biClrImportant = 0;
443
444		/* Write the BMP info values */
445		SDL_WriteLE32(dst, biSize);
446		SDL_WriteLE32(dst, biWidth);
447		SDL_WriteLE32(dst, biHeight);
448		SDL_WriteLE16(dst, biPlanes);
449		SDL_WriteLE16(dst, biBitCount);
450		SDL_WriteLE32(dst, biCompression);
451		SDL_WriteLE32(dst, biSizeImage);
452		SDL_WriteLE32(dst, biXPelsPerMeter);
453		SDL_WriteLE32(dst, biYPelsPerMeter);
454		SDL_WriteLE32(dst, biClrUsed);
455		SDL_WriteLE32(dst, biClrImportant);
456
457		/* Write the palette (in BGR color order) */
458		if ( surface->format->palette ) {
459			SDL_Color *colors;
460			int       ncolors;
461
462			colors = surface->format->palette->colors;
463			ncolors = surface->format->palette->ncolors;
464			for ( i=0; i<ncolors; ++i ) {
465				SDL_RWwrite(dst, &colors[i].b, 1, 1);
466				SDL_RWwrite(dst, &colors[i].g, 1, 1);
467				SDL_RWwrite(dst, &colors[i].r, 1, 1);
468				SDL_RWwrite(dst, &colors[i].unused, 1, 1);
469			}
470		}
471
472		/* Write the bitmap offset */
473		bfOffBits = SDL_RWtell(dst)-fp_offset;
474		if ( SDL_RWseek(dst, fp_offset+10, RW_SEEK_SET) < 0 ) {
475			SDL_Error(SDL_EFSEEK);
476		}
477		SDL_WriteLE32(dst, bfOffBits);
478		if ( SDL_RWseek(dst, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
479			SDL_Error(SDL_EFSEEK);
480		}
481
482		/* Write the bitmap image upside down */
483		bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
484		pad  = ((bw%4) ? (4-(bw%4)) : 0);
485		while ( bits > (Uint8 *)surface->pixels ) {
486			bits -= surface->pitch;
487			if ( SDL_RWwrite(dst, bits, 1, bw) != bw) {
488				SDL_Error(SDL_EFWRITE);
489				break;
490			}
491			if ( pad ) {
492				const Uint8 padbyte = 0;
493				for ( i=0; i<pad; ++i ) {
494					SDL_RWwrite(dst, &padbyte, 1, 1);
495				}
496			}
497		}
498
499		/* Write the BMP file size */
500		bfSize = SDL_RWtell(dst)-fp_offset;
501		if ( SDL_RWseek(dst, fp_offset+2, RW_SEEK_SET) < 0 ) {
502			SDL_Error(SDL_EFSEEK);
503		}
504		SDL_WriteLE32(dst, bfSize);
505		if ( SDL_RWseek(dst, fp_offset+bfSize, RW_SEEK_SET) < 0 ) {
506			SDL_Error(SDL_EFSEEK);
507		}
508
509		/* Close it up.. */
510		SDL_UnlockSurface(surface);
511		if ( surface != saveme ) {
512			SDL_FreeSurface(surface);
513		}
514	}
515
516	if ( freedst && dst ) {
517		SDL_RWclose(dst);
518	}
519	return((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
520}
521