1 /* Copyright (C)2004 Landmark Graphics Corporation
2  * Copyright (C)2005 Sun Microsystems, Inc.
3  * Copyright (C)2014, 2017-2019 D. R. Commander
4  *
5  * This library is free software and may be redistributed and/or modified under
6  * the terms of the wxWindows Library License, Version 3.1 or (at your option)
7  * any later version.  The full license is in the LICENSE.txt file included
8  * with this distribution.
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
13  * wxWindows Library License for more details.
14  */
15 
16 #include <fcntl.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #ifdef _WIN32
24 	#include <io.h>
25 #else
26 	#include <unistd.h>
27 #endif
28 #include "vglutil.h"
29 #include "bmp.h"
30 
31 
32 #ifndef BI_RGB
33 #define BI_RGB  0L
34 #endif
35 
36 
37 #define BMPHDRSIZE  54
38 typedef struct
39 {
40 	unsigned short bfType;
41 	unsigned int bfSize;
42 	unsigned short bfReserved1, bfReserved2;
43 	unsigned int bfOffBits;
44 
45 	unsigned int biSize;
46 	int biWidth, biHeight;
47 	unsigned short biPlanes, biBitCount;
48 	unsigned int biCompression, biSizeImage;
49 	int biXPelsPerMeter, biYPelsPerMeter;
50 	unsigned int biClrUsed, biClrImportant;
51 } BitmapHeader;
52 
53 
54 static const char *errorStr = "No error";
55 
56 
57 #define THROW(m) \
58 { \
59 	errorStr = m;  ret = -1;  goto finally; \
60 }
61 #define TRY_UNIX(f) \
62 { \
63 	if((f) == -1) THROW(strerror(errno)); \
64 }
65 #define CATCH(f) \
66 { \
67 	if((f) == -1) \
68 	{ \
69 		ret = -1;  goto finally; \
70 	} \
71 }
72 
73 
74 #define READ(fd, addr, size) \
75 	if((bytesRead = read(fd, addr, (size))) == -1) THROW(strerror(errno)); \
76 	if(bytesRead != (size)) THROW("Read error");
77 
78 #define WRITE(fd, addr, size) \
79 	if((bytesWritten = write(fd, addr, (size))) == -1) THROW(strerror(errno)); \
80 	if(bytesWritten != (size)) THROW("Write error");
81 
82 
pixelConvert(unsigned char * srcBuf,int width,int srcPitch,int height,PF * srcpf,unsigned char * dstBuf,int dstPitch,PF * dstpf,int flip)83 static void pixelConvert(unsigned char *srcBuf, int width, int srcPitch,
84 	int height, PF *srcpf, unsigned char *dstBuf, int dstPitch, PF *dstpf,
85 	int flip)
86 {
87 	if(flip)
88 	{
89 		srcBuf = &srcBuf[srcPitch * (height - 1)];
90 		srcPitch = -srcPitch;
91 	}
92 	srcpf->convert(srcBuf, width, srcPitch, height, dstBuf, dstPitch, dstpf);
93 	while(height--)
94 	{
95 		if(width * dstpf->size != dstPitch)
96 			memset(&dstBuf[width * dstpf->size], 0, dstPitch - width * dstpf->size);
97 		dstBuf += dstPitch;
98 	}
99 }
100 
101 
ppm_load(int * fd,unsigned char ** buf,int * width,int align,int * height,PF * pf,enum BMPORN orientation,int ascii)102 static int ppm_load(int *fd, unsigned char **buf, int *width, int align,
103 	int *height, PF *pf, enum BMPORN orientation, int ascii)
104 {
105 	FILE *file = NULL;  int ret = 0, scaleFactor, dstPitch;
106 	unsigned char *tempbuf = NULL;  char temps[255], temps2[255];
107 	int numRead = 0, totalRead = 0, pixel[3], i, j;
108 
109 	if((file = fdopen(*fd, "r")) == NULL) THROW(strerror(errno));
110 
111 	do
112 	{
113 		if(!fgets(temps, 255, file)) THROW("Read error");
114 		if(strlen(temps) == 0 || temps[0] == '\n') continue;
115 		if(sscanf(temps, "%s", temps2) == 1 && temps2[1] == '#') continue;
116 		switch(totalRead)
117 		{
118 			case 0:
119 				if((numRead = sscanf(temps, "%d %d %d", width, height,
120 					&scaleFactor)) == EOF)
121 					THROW("Read error");
122 				break;
123 			case 1:
124 				if((numRead = sscanf(temps, "%d %d", height, &scaleFactor)) == EOF)
125 					THROW("Read error");
126 				break;
127 			case 2:
128 				if((numRead = sscanf(temps, "%d", &scaleFactor)) == EOF)
129 					THROW("Read error");
130 				break;
131 		}
132 		totalRead += numRead;
133 	} while(totalRead < 3);
134 	if((*width) < 1 || (*height) < 1 || scaleFactor < 1)
135 		THROW("Corrupt PPM header");
136 
137 	dstPitch = (((*width) * pf->size) + (align - 1)) & (~(align - 1));
138 	if((*buf = (unsigned char *)malloc(dstPitch * (*height))) == NULL)
139 		THROW("Memory allocation error");
140 	if(ascii)
141 	{
142 		for(j = 0; j < *height; j++)
143 		{
144 			for(i = 0; i < *width; i++)
145 			{
146 				if(fscanf(file, "%d%d%d", &pixel[0], &pixel[1], &pixel[2]) != 3)
147 					THROW("Read error");
148 				pf->setRGB(&(*buf)[j * dstPitch + i * pf->size],
149 					pixel[0] * 255 / scaleFactor, pixel[1] * 255 / scaleFactor,
150 					pixel[2] * 255 / scaleFactor);
151 			}
152 		}
153 	}
154 	else
155 	{
156 		if(scaleFactor != 255)
157 			THROW("Binary PPMs must have 8-bit components");
158 		if((tempbuf = (unsigned char *)malloc((*width) * (*height) * 3)) == NULL)
159 			THROW("Memory allocation error");
160 		if(fread(tempbuf, (*width) * (*height) * 3, 1, file) != 1)
161 			THROW("Read error");
162 		pixelConvert(tempbuf, *width, (*width) * 3, *height, pf_get(PF_RGB), *buf,
163 			dstPitch, pf, orientation == BMPORN_BOTTOMUP);
164 	}
165 
166 	finally:
167 	if(file) { fclose(file);  *fd = -1; }
168 	free(tempbuf);
169 	return ret;
170 }
171 
172 
bmp_load(char * filename,unsigned char ** buf,int * width,int align,int * height,int format,enum BMPORN orientation)173 int bmp_load(char *filename, unsigned char **buf, int *width, int align,
174 	int *height, int format, enum BMPORN orientation)
175 {
176 	int fd = -1, bytesRead, srcPitch, srcPixelSize, dstPitch, ret = 0;
177 	enum BMPORN srcOrientation = BMPORN_BOTTOMUP;
178 	unsigned char *tempbuf = NULL;
179 	BitmapHeader bh;  int flags = O_RDONLY;
180 	PF *pf = pf_get(format);
181 
182 	#ifdef _WIN32
183 	flags |= O_BINARY;
184 	#endif
185 	if(!filename || !buf || !width || !height || pf->bpc < 8 || align < 1)
186 		THROW("Invalid argument to bmp_load()");
187 	if((align & (align - 1)) != 0)
188 		THROW("Alignment must be a power of 2");
189 	TRY_UNIX(fd = open(filename, flags));
190 
191 	READ(fd, &bh.bfType, sizeof(unsigned short));
192 	if(!LittleEndian()) bh.bfType = BYTESWAP16(bh.bfType);
193 
194 	if(bh.bfType == 0x3650)
195 	{
196 		CATCH(ppm_load(&fd, buf, width, align, height, pf, orientation, 0));
197 		goto finally;
198 	}
199 	if(bh.bfType == 0x3350)
200 	{
201 		CATCH(ppm_load(&fd, buf, width, align, height, pf, orientation, 1));
202 		goto finally;
203 	}
204 
205 	READ(fd, &bh.bfSize, sizeof(unsigned int));
206 	READ(fd, &bh.bfReserved1, sizeof(unsigned short));
207 	READ(fd, &bh.bfReserved2, sizeof(unsigned short));
208 	READ(fd, &bh.bfOffBits, sizeof(unsigned int));
209 	READ(fd, &bh.biSize, sizeof(unsigned int));
210 	READ(fd, &bh.biWidth, sizeof(int));
211 	READ(fd, &bh.biHeight, sizeof(int));
212 	READ(fd, &bh.biPlanes, sizeof(unsigned short));
213 	READ(fd, &bh.biBitCount, sizeof(unsigned short));
214 	READ(fd, &bh.biCompression, sizeof(unsigned int));
215 	READ(fd, &bh.biSizeImage, sizeof(unsigned int));
216 	READ(fd, &bh.biXPelsPerMeter, sizeof(int));
217 	READ(fd, &bh.biYPelsPerMeter, sizeof(int));
218 	READ(fd, &bh.biClrUsed, sizeof(unsigned int));
219 	READ(fd, &bh.biClrImportant, sizeof(unsigned int));
220 
221 	if(!LittleEndian())
222 	{
223 		bh.bfSize = BYTESWAP(bh.bfSize);
224 		bh.bfOffBits = BYTESWAP(bh.bfOffBits);
225 		bh.biSize = BYTESWAP(bh.biSize);
226 		bh.biWidth = BYTESWAP(bh.biWidth);
227 		bh.biHeight = BYTESWAP(bh.biHeight);
228 		bh.biPlanes = BYTESWAP16(bh.biPlanes);
229 		bh.biBitCount = BYTESWAP16(bh.biBitCount);
230 		bh.biCompression = BYTESWAP(bh.biCompression);
231 		bh.biSizeImage = BYTESWAP(bh.biSizeImage);
232 		bh.biXPelsPerMeter = BYTESWAP(bh.biXPelsPerMeter);
233 		bh.biYPelsPerMeter = BYTESWAP(bh.biYPelsPerMeter);
234 		bh.biClrUsed = BYTESWAP(bh.biClrUsed);
235 		bh.biClrImportant = BYTESWAP(bh.biClrImportant);
236 	}
237 
238 	if(bh.bfType != 0x4d42 || bh.bfOffBits < BMPHDRSIZE || bh.biWidth < 1
239 		|| bh.biHeight == 0)
240 		THROW("Corrupt bitmap header");
241 	if((bh.biBitCount != 24 && bh.biBitCount != 32)
242 		|| bh.biCompression != BI_RGB)
243 		THROW("Only uncompessed RGB bitmaps are supported");
244 
245 	*width = bh.biWidth;  *height = bh.biHeight;
246 	srcPixelSize = bh.biBitCount / 8;
247 	if(*height < 0) { *height = -(*height);  srcOrientation = BMPORN_TOPDOWN; }
248 	srcPitch = (((*width) * srcPixelSize) + 3) & (~3);
249 	dstPitch = (((*width) * pf->size) + (align - 1)) & (~(align - 1));
250 
251 	if(srcPitch * (*height) + bh.bfOffBits != bh.bfSize)
252 		THROW("Corrupt bitmap header");
253 	if((tempbuf = (unsigned char *)malloc(srcPitch * (*height))) == NULL
254 		|| (*buf = (unsigned char *)malloc(dstPitch * (*height))) == NULL)
255 		THROW("Memory allocation error");
256 	if(lseek(fd, (long)bh.bfOffBits, SEEK_SET) != (long)bh.bfOffBits)
257 		THROW(strerror(errno));
258 	TRY_UNIX(bytesRead = read(fd, tempbuf, srcPitch * (*height)));
259 	if(bytesRead != srcPitch * (*height)) THROW("Read error");
260 
261 	pixelConvert(tempbuf, *width, srcPitch, *height, pf_get(PF_BGR), *buf,
262 		dstPitch, pf, srcOrientation != orientation);
263 
264 	finally:
265 	free(tempbuf);
266 	if(fd != -1) close(fd);
267 	return ret;
268 }
269 
270 
ppm_save(char * filename,unsigned char * buf,int width,int pitch,int height,PF * pf,enum BMPORN orientation)271 static int ppm_save(char *filename, unsigned char *buf, int width, int pitch,
272 	int height, PF *pf, enum BMPORN orientation)
273 {
274 	FILE *file = NULL;  int ret = 0;
275 	unsigned char *tempbuf = NULL;
276 
277 	if((file = fopen(filename, "wb")) == NULL) THROW(strerror(errno));
278 	if(fprintf(file, "P6\n") < 1) THROW("Write error");
279 	if(fprintf(file, "%d %d\n", width, height) < 1) THROW("Write error");
280 	if(fprintf(file, "255\n") < 1) THROW("Write error");
281 
282 	if((tempbuf = (unsigned char *)malloc(width * height * 3)) == NULL)
283 		THROW("Memory allocation error");
284 
285 	pixelConvert(buf, width, pitch, height, pf, tempbuf, width * 3,
286 		pf_get(PF_RGB), orientation == BMPORN_BOTTOMUP);
287 
288 	if((fwrite(tempbuf, width * height * 3, 1, file)) != 1)
289 		THROW("Write error");
290 
291 	finally:
292 	free(tempbuf);
293 	if(file) fclose(file);
294 	return ret;
295 }
296 
297 
bmp_save(char * filename,unsigned char * buf,int width,int pitch,int height,int format,enum BMPORN orientation)298 int bmp_save(char *filename, unsigned char *buf, int width, int pitch,
299 	int height, int format, enum BMPORN orientation)
300 {
301 	int fd = -1, bytesWritten, dstPitch, ret = 0;
302 	int flags = O_RDWR | O_CREAT | O_TRUNC;
303 	unsigned char *tempbuf = NULL;  char *temp;
304 	BitmapHeader bh;  int mode;
305 	PF *pf = pf_get(format);
306 
307 	#ifdef _WIN32
308 	flags |= O_BINARY;  mode = _S_IREAD | _S_IWRITE;
309 	#else
310 	mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
311 	#endif
312 	if(!filename || !buf || width < 1 || height < 1 || pf->bpc < 8 || pitch < 0)
313 		THROW("Invalid argument to bmp_save()");
314 
315 	if(pitch == 0) pitch = width * pf->size;
316 
317 	if((temp = strrchr(filename, '.')) != NULL)
318 	{
319 		if(!stricmp(temp, ".ppm"))
320 			return ppm_save(filename, buf, width, pitch, height, pf, orientation);
321 	}
322 
323 	TRY_UNIX(fd = open(filename, flags, mode));
324 	dstPitch = ((width * 3) + 3) & (~3);
325 
326 	bh.bfType = 0x4d42;
327 	bh.bfSize = BMPHDRSIZE + dstPitch * height;
328 	bh.bfReserved1 = 0;  bh.bfReserved2 = 0;
329 	bh.bfOffBits = BMPHDRSIZE;
330 	bh.biSize = 40;
331 	bh.biWidth = width;  bh.biHeight = height;
332 	bh.biPlanes = 0;  bh.biBitCount = 24;
333 	bh.biCompression = BI_RGB;  bh.biSizeImage = 0;
334 	bh.biXPelsPerMeter = 0;  bh.biYPelsPerMeter = 0;
335 	bh.biClrUsed = 0;  bh.biClrImportant = 0;
336 
337 	if(!LittleEndian())
338 	{
339 		bh.bfType = BYTESWAP16(bh.bfType);
340 		bh.bfSize = BYTESWAP(bh.bfSize);
341 		bh.bfOffBits = BYTESWAP(bh.bfOffBits);
342 		bh.biSize = BYTESWAP(bh.biSize);
343 		bh.biWidth = BYTESWAP(bh.biWidth);
344 		bh.biHeight = BYTESWAP(bh.biHeight);
345 		bh.biPlanes = BYTESWAP16(bh.biPlanes);
346 		bh.biBitCount = BYTESWAP16(bh.biBitCount);
347 		bh.biCompression = BYTESWAP(bh.biCompression);
348 		bh.biSizeImage = BYTESWAP(bh.biSizeImage);
349 		bh.biXPelsPerMeter = BYTESWAP(bh.biXPelsPerMeter);
350 		bh.biYPelsPerMeter = BYTESWAP(bh.biYPelsPerMeter);
351 		bh.biClrUsed = BYTESWAP(bh.biClrUsed);
352 		bh.biClrImportant = BYTESWAP(bh.biClrImportant);
353 	}
354 
355 	WRITE(fd, &bh.bfType, sizeof(unsigned short));
356 	WRITE(fd, &bh.bfSize, sizeof(unsigned int));
357 	WRITE(fd, &bh.bfReserved1, sizeof(unsigned short));
358 	WRITE(fd, &bh.bfReserved2, sizeof(unsigned short));
359 	WRITE(fd, &bh.bfOffBits, sizeof(unsigned int));
360 	WRITE(fd, &bh.biSize, sizeof(unsigned int));
361 	WRITE(fd, &bh.biWidth, sizeof(int));
362 	WRITE(fd, &bh.biHeight, sizeof(int));
363 	WRITE(fd, &bh.biPlanes, sizeof(unsigned short));
364 	WRITE(fd, &bh.biBitCount, sizeof(unsigned short));
365 	WRITE(fd, &bh.biCompression, sizeof(unsigned int));
366 	WRITE(fd, &bh.biSizeImage, sizeof(unsigned int));
367 	WRITE(fd, &bh.biXPelsPerMeter, sizeof(int));
368 	WRITE(fd, &bh.biYPelsPerMeter, sizeof(int));
369 	WRITE(fd, &bh.biClrUsed, sizeof(unsigned int));
370 	WRITE(fd, &bh.biClrImportant, sizeof(unsigned int));
371 
372 	if((tempbuf = (unsigned char *)malloc(dstPitch * height)) == NULL)
373 		THROW("Memory allocation error");
374 
375 	pixelConvert(buf, width, pitch, height, pf, tempbuf, dstPitch,
376 		pf_get(PF_BGR), orientation != BMPORN_BOTTOMUP);
377 
378 	if((bytesWritten =
379 		write(fd, tempbuf, dstPitch * height)) != dstPitch * height)
380 		THROW(strerror(errno));
381 
382 	finally:
383 	free(tempbuf);
384 	if(fd != -1) close(fd);
385 	return ret;
386 }
387 
388 
bmp_geterr(void)389 const char *bmp_geterr(void)
390 {
391 	return errorStr;
392 }
393