1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ultima/nuvie/core/nuvie_defs.h"
24 #include "ultima/nuvie/files/nuvie_bmp_file.h"
25 
26 namespace Ultima {
27 namespace Nuvie {
28 
29 #define NUVIEBMPFILE_MAGIC 0x4d42 // 'BM'
30 
NuvieBmpFile()31 NuvieBmpFile::NuvieBmpFile() {
32 	data = NULL;
33 	prev_width = 0;
34 	prev_height = 0;
35 	prev_bits = 0;
36 	bmp_line_width = 0;
37 	memset(&header, 0, sizeof(header));
38 	memset(&infoHeader, 0, sizeof(infoHeader));
39 }
40 
~NuvieBmpFile()41 NuvieBmpFile::~NuvieBmpFile() {
42 	if (data != NULL)
43 		free(data);
44 }
45 
initNewBlankImage(sint32 width,sint32 height,const unsigned char * pal)46 bool NuvieBmpFile::initNewBlankImage(sint32 width, sint32 height, const unsigned char *pal) {
47 	infoHeader.size = 40;
48 	infoHeader.width = width;
49 	infoHeader.height = height;
50 	infoHeader.planes = 1;
51 	infoHeader.bits = 8;
52 	infoHeader.compression = 0;
53 	infoHeader.imagesize = 0;
54 	infoHeader.xresolution = 0; //FIXME
55 	infoHeader.yresolution = 0; //FIXME
56 	infoHeader.ncolours = 256;
57 	infoHeader.importantcolours = 256;
58 
59 	bmp_line_width = infoHeader.width;
60 	if (bmp_line_width % 4 != 0) {
61 		bmp_line_width += (4 - (bmp_line_width % 4));
62 	}
63 
64 	header.type = NUVIEBMPFILE_MAGIC;
65 	header.reserved1 = 0;
66 	header.reserved2 = 0;
67 	header.offset = NUVIEBMP_HEADER_SIZE + NUVIEBMP_INFOHEADER_SIZE + 256 * 4;
68 	header.size = header.offset + bmp_line_width * infoHeader.height;
69 
70 	memcpy(&palette, pal, sizeof(palette));
71 
72 	data = (unsigned char *)malloc(infoHeader.width * infoHeader.height);
73 	if (!data) {
74 		return handleError("Allocating pixel data");
75 	}
76 
77 	memset(data, 0, infoHeader.width * infoHeader.height);
78 
79 	return true;
80 }
81 
load(Std::string filename)82 bool NuvieBmpFile::load(Std::string filename) {
83 	NuvieIOFileRead file;
84 
85 	if (filename.length() == 0)
86 		return handleError("zero byte file");
87 
88 	if (!file.open(filename)) {
89 		return handleError("opening file");
90 	}
91 
92 	if (file.get_size() < 0x36) { //invalid header.
93 		return handleError("filesize < 0x36");
94 	}
95 
96 	header.type = file.read2();
97 	header.size = file.read4();
98 	header.reserved1 = file.read2();
99 	header.reserved2 = file.read2();
100 	header.offset = file.read4();
101 
102 	infoHeader.size = file.read4();
103 	infoHeader.width = file.read4();
104 	infoHeader.height = file.read4();
105 	infoHeader.planes = file.read2();
106 	infoHeader.bits = file.read2();
107 	infoHeader.compression = file.read4();
108 	infoHeader.imagesize = file.read4();
109 	infoHeader.xresolution = file.read4();
110 	infoHeader.yresolution = file.read4();
111 	infoHeader.ncolours = file.read4();
112 	infoHeader.importantcolours = file.read4();
113 
114 	if (header.type != NUVIEBMPFILE_MAGIC) { //invalid magic.
115 		return handleError("invalid BMP magic.");
116 	}
117 
118 	if (infoHeader.bits != 8 && infoHeader.bits != 24) {
119 		return handleError("only 256 colour bitmaps supported.");
120 	}
121 
122 	if (infoHeader.compression != 0) { // && infoHeader.compression != 2)
123 		return handleError("only uncompressed BMP files are supported");
124 		//return handleError("only raw and bi_rle8 compression formats are supported.");
125 
126 		//FIXME need to handle rle compression.
127 	}
128 
129 	if (infoHeader.bits == 8) {
130 		for (uint32 i = 0; i < infoHeader.ncolours; i++) {
131 			uint8 b = file.read1();
132 			uint8 g = file.read1();
133 			uint8 r = file.read1();
134 			file.read1(); // 0
135 			palette[i] = (uint32)r | (uint32)(g << 8) | (uint32)(b << 16);
136 		}
137 	}
138 
139 	file.seekStart();
140 	file.seek(header.offset);
141 
142 	uint16 bytes_per_pixel = infoHeader.bits / 8;
143 	bmp_line_width = infoHeader.width * bytes_per_pixel;
144 	if (bmp_line_width % 4 != 0) {
145 		bmp_line_width += (4 - (bmp_line_width % 4));
146 	}
147 
148 	if (data == NULL || infoHeader.width != prev_width || infoHeader.height != prev_height || prev_bits != infoHeader.bits) {
149 		if (data) {
150 			free(data);
151 		}
152 		data = (unsigned char *)malloc(infoHeader.width * infoHeader.height * bytes_per_pixel);
153 		prev_width = infoHeader.width;
154 		prev_height = infoHeader.height;
155 		prev_bits = infoHeader.bits;
156 		if (data == NULL) {
157 			return handleError("allocating memory for image");
158 		}
159 	}
160 
161 	uint32 end = header.offset + bmp_line_width * infoHeader.height;
162 
163 	uint32 data_width = infoHeader.width * bytes_per_pixel;
164 
165 	for (sint32 i = 0; i < infoHeader.height; i++) {
166 		file.seek(end - bmp_line_width - (bmp_line_width * i));
167 		file.readToBuf(&data[i * data_width], data_width);
168 	}
169 
170 	return true;
171 }
172 
save(Std::string filename)173 bool NuvieBmpFile::save(Std::string filename) {
174 	NuvieIOFileWrite file;
175 
176 	if (!file.open(filename)) {
177 		return handleError("Opening " + filename + ".");
178 	}
179 
180 	file.write2(header.type);
181 	file.write4(header.size);
182 	file.write2(header.reserved1);
183 	file.write2(header.reserved2);
184 	file.write4(header.offset);
185 
186 	file.write4(infoHeader.size);
187 	file.write4(infoHeader.width);
188 	file.write4(infoHeader.height);
189 	file.write2(infoHeader.planes);
190 	file.write2(infoHeader.bits);
191 	file.write4(infoHeader.compression);
192 	file.write4(infoHeader.imagesize);
193 	file.write4(infoHeader.xresolution);
194 	file.write4(infoHeader.yresolution);
195 	file.write4(infoHeader.ncolours);
196 	file.write4(infoHeader.importantcolours);
197 
198 	if (infoHeader.bits == 8) {
199 		for (uint32 i = 0; i < infoHeader.ncolours; i++) {
200 			file.write1((palette[i] >> 16) & 0xff); //B
201 			file.write1((palette[i] >> 8) & 0xff);  //G
202 			file.write1(palette[i] & 0xff);         //R
203 			file.write1((palette[i] >> 24) & 0xff); //A
204 		}
205 		write8BitData(&file);
206 	} else {
207 		//FIXME write out 24bit data here.
208 	}
209 
210 	file.close();
211 	return true;
212 }
213 
write8BitData(NuvieIOFileWrite * file)214 void NuvieBmpFile::write8BitData(NuvieIOFileWrite *file) {
215 	uint32 i;
216 	for (i = infoHeader.height; i > 0; i--) {
217 		file->writeBuf(&data[(i - 1)*infoHeader.width], infoHeader.width);
218 		if ((sint32)bmp_line_width > infoHeader.width) {
219 			//write out padding bytes.
220 			for (uint8 j = 0; j < bmp_line_width - infoHeader.width; j++) {
221 				file->write1(0);
222 			}
223 		}
224 	}
225 }
226 
handleError(Std::string error)227 bool NuvieBmpFile::handleError(Std::string error) {
228 	if (data) {
229 		free(data);
230 		data = NULL;
231 	}
232 
233 	DEBUG(0, LEVEL_ERROR, error.c_str());
234 
235 	return false;
236 }
237 
getTile()238 Tile *NuvieBmpFile::getTile() {
239 	if (infoHeader.width != 16 || infoHeader.height != 16 || infoHeader.bits != 8) {
240 		return NULL;
241 	}
242 
243 	Tile *t = (Tile *)malloc(sizeof(Tile));
244 	if (t == NULL) {
245 		return NULL;
246 	}
247 	memset(t, 0, sizeof(Tile));
248 	memcpy(t->data, data, 256);
249 
250 	return t;
251 }
252 
getRawIndexedData()253 unsigned char *NuvieBmpFile::getRawIndexedData() {
254 	if (infoHeader.bits != 8) {
255 		return NULL;
256 	}
257 
258 	return data;
259 }
260 
getRawIndexedDataCopy()261 unsigned char *NuvieBmpFile::getRawIndexedDataCopy() {
262 	if (data == NULL || infoHeader.bits != 8) {
263 		return NULL;
264 	}
265 
266 	unsigned char *copy = (unsigned char *)malloc(infoHeader.width * infoHeader.height);
267 	if (copy == NULL) {
268 		return NULL;
269 	}
270 	memcpy(copy, data, infoHeader.width * infoHeader.height);
271 	return copy;
272 }
273 
getSdlSurface32(Std::string filename)274 Graphics::ManagedSurface *NuvieBmpFile::getSdlSurface32(Std::string filename) {
275 	load(filename);
276 	return getSdlSurface32();
277 }
278 
getSdlSurface32()279 Graphics::ManagedSurface *NuvieBmpFile::getSdlSurface32() {
280 	if (data == NULL) {
281 		return NULL;
282 	}
283 
284 	Graphics::ManagedSurface *surface = new Graphics::ManagedSurface(
285 		infoHeader.width, infoHeader.height,
286 		Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)
287 	);
288 
289 	unsigned char *src_buf = data;
290 	Graphics::Surface s = surface->getSubArea(Common::Rect(0, 0, surface->w, surface->h));
291 	uint32 *pixels = (uint32 *)s.getPixels();
292 
293 	if (infoHeader.bits == 8) {
294 		for (sint32 i = 0; i < infoHeader.height; i++) {
295 			for (sint32 j = 0; j < infoHeader.width; j++) {
296 				pixels[j] = palette[src_buf[j]];
297 			}
298 			src_buf += infoHeader.width;
299 			pixels += infoHeader.width;
300 		}
301 	} else { //bits == 24
302 		for (sint32 i = 0; i < infoHeader.height; i++) {
303 			for (sint32 j = 0; j < infoHeader.width; j++) {
304 				pixels[j] = (uint32)src_buf[j * 3 + 2] | (uint32)(src_buf[j * 3 + 1] << 8) | (uint32)(src_buf[j * 3 + 0] << 16);
305 			}
306 			src_buf += infoHeader.width * 3;
307 			pixels += infoHeader.width;
308 		}
309 	}
310 	return surface;
311 }
312 
313 } // End of namespace Nuvie
314 } // End of namespace Ultima
315