1/* Copyright (C)2004 Landmark Graphics Corporation
2 * Copyright (C)2005 Sun Microsystems, Inc.
3 * Copyright (C)2010, 2012 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 "./tjutil.h"
29#include "./bmp.h"
30
31#define byteswap(i) ( \
32	(((i) & 0xff000000) >> 24) | \
33	(((i) & 0x00ff0000) >>  8) | \
34	(((i) & 0x0000ff00) <<  8) | \
35	(((i) & 0x000000ff) << 24) )
36
37#define byteswap16(i) ( \
38	(((i) & 0xff00) >> 8) | \
39	(((i) & 0x00ff) << 8) )
40
41static __inline int littleendian(void)
42{
43	unsigned int value=1;
44	unsigned char *ptr=(unsigned char *)(&value);
45	if(ptr[0]==1 && ptr[3]==0) return 1;
46	else return 0;
47}
48
49#ifndef BI_BITFIELDS
50#define BI_BITFIELDS 3L
51#endif
52#ifndef BI_RGB
53#define BI_RGB 0L
54#endif
55
56#define BMPHDRSIZE 54
57typedef struct _bmphdr
58{
59	unsigned short bfType;
60	unsigned int bfSize;
61	unsigned short bfReserved1, bfReserved2;
62	unsigned int bfOffBits;
63
64	unsigned int biSize;
65	int biWidth, biHeight;
66	unsigned short biPlanes, biBitCount;
67	unsigned int biCompression, biSizeImage;
68	int biXPelsPerMeter, biYPelsPerMeter;
69	unsigned int biClrUsed, biClrImportant;
70} bmphdr;
71
72static const char *__bmperr="No error";
73
74static const int ps[BMPPIXELFORMATS]={3, 4, 3, 4, 4, 4};
75static const int roffset[BMPPIXELFORMATS]={0, 0, 2, 2, 3, 1};
76static const int goffset[BMPPIXELFORMATS]={1, 1, 1, 1, 2, 2};
77static const int boffset[BMPPIXELFORMATS]={2, 2, 0, 0, 1, 3};
78
79#define _throw(m) {__bmperr=m;  retcode=-1;  goto finally;}
80#define _unix(f) {if((f)==-1) _throw(strerror(errno));}
81#define _catch(f) {if((f)==-1) {retcode=-1;  goto finally;}}
82
83#define readme(fd, addr, size) \
84	if((bytesread=read(fd, addr, (size)))==-1) _throw(strerror(errno)); \
85	if(bytesread!=(size)) _throw("Read error");
86
87void pixelconvert(unsigned char *srcbuf, enum BMPPIXELFORMAT srcformat,
88	int srcpitch, unsigned char *dstbuf, enum BMPPIXELFORMAT dstformat, int dstpitch,
89	int w, int h, int flip)
90{
91	unsigned char *srcptr, *srcptr0, *dstptr, *dstptr0;
92	int i, j;
93
94	srcptr=flip? &srcbuf[srcpitch*(h-1)]:srcbuf;
95	for(j=0, dstptr=dstbuf; j<h; j++,
96		srcptr+=flip? -srcpitch:srcpitch, dstptr+=dstpitch)
97	{
98		for(i=0, srcptr0=srcptr, dstptr0=dstptr; i<w; i++,
99			srcptr0+=ps[srcformat], dstptr0+=ps[dstformat])
100		{
101			dstptr0[roffset[dstformat]]=srcptr0[roffset[srcformat]];
102			dstptr0[goffset[dstformat]]=srcptr0[goffset[srcformat]];
103			dstptr0[boffset[dstformat]]=srcptr0[boffset[srcformat]];
104		}
105	}
106}
107
108int loadppm(int *fd, unsigned char **buf, int *w, int *h,
109	enum BMPPIXELFORMAT f, int align, int dstbottomup, int ascii)
110{
111	FILE *fs=NULL;  int retcode=0, scalefactor, dstpitch;
112	unsigned char *tempbuf=NULL;  char temps[255], temps2[255];
113	int numread=0, totalread=0, pixel[3], i, j;
114
115	if((fs=fdopen(*fd, "r"))==NULL) _throw(strerror(errno));
116
117	do
118	{
119		if(!fgets(temps, 255, fs)) _throw("Read error");
120		if(strlen(temps)==0 || temps[0]=='\n') continue;
121		if(sscanf(temps, "%s", temps2)==1 && temps2[1]=='#') continue;
122		switch(totalread)
123		{
124			case 0:
125				if((numread=sscanf(temps, "%d %d %d", w, h, &scalefactor))==EOF)
126					_throw("Read error");
127				break;
128			case 1:
129				if((numread=sscanf(temps, "%d %d", h, &scalefactor))==EOF)
130					_throw("Read error");
131				break;
132			case 2:
133				if((numread=sscanf(temps, "%d", &scalefactor))==EOF)
134					_throw("Read error");
135				break;
136		}
137		totalread+=numread;
138	} while(totalread<3);
139	if((*w)<1 || (*h)<1 || scalefactor<1) _throw("Corrupt PPM header");
140
141	dstpitch=(((*w)*ps[f])+(align-1))&(~(align-1));
142	if((*buf=(unsigned char *)malloc(dstpitch*(*h)))==NULL)
143		_throw("Memory allocation error");
144	if(ascii)
145	{
146		for(j=0; j<*h; j++)
147		{
148			for(i=0; i<*w; i++)
149			{
150				if(fscanf(fs, "%d%d%d", &pixel[0], &pixel[1], &pixel[2])!=3)
151					_throw("Read error");
152				(*buf)[j*dstpitch+i*ps[f]+roffset[f]]=(unsigned char)(pixel[0]*255/scalefactor);
153				(*buf)[j*dstpitch+i*ps[f]+goffset[f]]=(unsigned char)(pixel[1]*255/scalefactor);
154				(*buf)[j*dstpitch+i*ps[f]+boffset[f]]=(unsigned char)(pixel[2]*255/scalefactor);
155			}
156		}
157	}
158	else
159	{
160		if(scalefactor!=255)
161			_throw("Binary PPMs must have 8-bit components");
162		if((tempbuf=(unsigned char *)malloc((*w)*(*h)*3))==NULL)
163			_throw("Memory allocation error");
164		if(fread(tempbuf, (*w)*(*h)*3, 1, fs)!=1) _throw("Read error");
165		pixelconvert(tempbuf, BMP_RGB, (*w)*3, *buf, f, dstpitch, *w, *h, dstbottomup);
166	}
167
168	finally:
169	if(fs) {fclose(fs);  *fd=-1;}
170	if(tempbuf) free(tempbuf);
171	return retcode;
172}
173
174
175int loadbmp(char *filename, unsigned char **buf, int *w, int *h,
176	enum BMPPIXELFORMAT f, int align, int dstbottomup)
177{
178	int fd=-1, bytesread, srcpitch, srcbottomup=1, srcps, dstpitch,
179		retcode=0;
180	unsigned char *tempbuf=NULL;
181	bmphdr bh;  int flags=O_RDONLY;
182
183	dstbottomup=dstbottomup? 1:0;
184	#ifdef _WIN32
185	flags|=O_BINARY;
186	#endif
187	if(!filename || !buf || !w || !h || f<0 || f>BMPPIXELFORMATS-1 || align<1)
188		_throw("invalid argument to loadbmp()");
189	if((align&(align-1))!=0)
190		_throw("Alignment must be a power of 2");
191	_unix(fd=open(filename, flags));
192
193	readme(fd, &bh.bfType, sizeof(unsigned short));
194	if(!littleendian())	bh.bfType=byteswap16(bh.bfType);
195
196	if(bh.bfType==0x3650)
197	{
198		_catch(loadppm(&fd, buf, w, h, f, align, dstbottomup, 0));
199		goto finally;
200	}
201	if(bh.bfType==0x3350)
202	{
203		_catch(loadppm(&fd, buf, w, h, f, align, dstbottomup, 1));
204		goto finally;
205	}
206
207	readme(fd, &bh.bfSize, sizeof(unsigned int));
208	readme(fd, &bh.bfReserved1, sizeof(unsigned short));
209	readme(fd, &bh.bfReserved2, sizeof(unsigned short));
210	readme(fd, &bh.bfOffBits, sizeof(unsigned int));
211	readme(fd, &bh.biSize, sizeof(unsigned int));
212	readme(fd, &bh.biWidth, sizeof(int));
213	readme(fd, &bh.biHeight, sizeof(int));
214	readme(fd, &bh.biPlanes, sizeof(unsigned short));
215	readme(fd, &bh.biBitCount, sizeof(unsigned short));
216	readme(fd, &bh.biCompression, sizeof(unsigned int));
217	readme(fd, &bh.biSizeImage, sizeof(unsigned int));
218	readme(fd, &bh.biXPelsPerMeter, sizeof(int));
219	readme(fd, &bh.biYPelsPerMeter, sizeof(int));
220	readme(fd, &bh.biClrUsed, sizeof(unsigned int));
221	readme(fd, &bh.biClrImportant, sizeof(unsigned int));
222
223	if(!littleendian())
224	{
225		bh.bfSize=byteswap(bh.bfSize);
226		bh.bfOffBits=byteswap(bh.bfOffBits);
227		bh.biSize=byteswap(bh.biSize);
228		bh.biWidth=byteswap(bh.biWidth);
229		bh.biHeight=byteswap(bh.biHeight);
230		bh.biPlanes=byteswap16(bh.biPlanes);
231		bh.biBitCount=byteswap16(bh.biBitCount);
232		bh.biCompression=byteswap(bh.biCompression);
233		bh.biSizeImage=byteswap(bh.biSizeImage);
234		bh.biXPelsPerMeter=byteswap(bh.biXPelsPerMeter);
235		bh.biYPelsPerMeter=byteswap(bh.biYPelsPerMeter);
236		bh.biClrUsed=byteswap(bh.biClrUsed);
237		bh.biClrImportant=byteswap(bh.biClrImportant);
238	}
239
240	if(bh.bfType!=0x4d42 || bh.bfOffBits<BMPHDRSIZE
241	|| bh.biWidth<1 || bh.biHeight==0)
242		_throw("Corrupt bitmap header");
243	if((bh.biBitCount!=24 && bh.biBitCount!=32) || bh.biCompression!=BI_RGB)
244		_throw("Only uncompessed RGB bitmaps are supported");
245
246	*w=bh.biWidth;  *h=bh.biHeight;  srcps=bh.biBitCount/8;
247	if(*h<0) {*h=-(*h);  srcbottomup=0;}
248	srcpitch=(((*w)*srcps)+3)&(~3);
249	dstpitch=(((*w)*ps[f])+(align-1))&(~(align-1));
250
251	if(srcpitch*(*h)+bh.bfOffBits!=bh.bfSize) _throw("Corrupt bitmap header");
252	if((tempbuf=(unsigned char *)malloc(srcpitch*(*h)))==NULL
253	|| (*buf=(unsigned char *)malloc(dstpitch*(*h)))==NULL)
254		_throw("Memory allocation error");
255	if(lseek(fd, (long)bh.bfOffBits, SEEK_SET)!=(long)bh.bfOffBits)
256		_throw(strerror(errno));
257	_unix(bytesread=read(fd, tempbuf, srcpitch*(*h)));
258	if(bytesread!=srcpitch*(*h)) _throw("Read error");
259
260	pixelconvert(tempbuf, BMP_BGR, srcpitch, *buf, f, dstpitch, *w, *h,
261		srcbottomup!=dstbottomup);
262
263	finally:
264	if(tempbuf) free(tempbuf);
265	if(fd!=-1) close(fd);
266	return retcode;
267}
268
269#define writeme(fd, addr, size) \
270	if((byteswritten=write(fd, addr, (size)))==-1) _throw(strerror(errno)); \
271	if(byteswritten!=(size)) _throw("Write error");
272
273int saveppm(char *filename, unsigned char *buf, int w, int h,
274	enum BMPPIXELFORMAT f, int srcpitch, int srcbottomup)
275{
276	FILE *fs=NULL;  int retcode=0;
277	unsigned char *tempbuf=NULL;
278
279	if((fs=fopen(filename, "wb"))==NULL) _throw(strerror(errno));
280	if(fprintf(fs, "P6\n")<1) _throw("Write error");
281	if(fprintf(fs, "%d %d\n", w, h)<1) _throw("Write error");
282	if(fprintf(fs, "255\n")<1) _throw("Write error");
283
284	if((tempbuf=(unsigned char *)malloc(w*h*3))==NULL)
285		_throw("Memory allocation error");
286
287	pixelconvert(buf, f, srcpitch, tempbuf, BMP_RGB, w*3, w, h,
288		srcbottomup);
289
290	if((fwrite(tempbuf, w*h*3, 1, fs))!=1) _throw("Write error");
291
292	finally:
293	if(tempbuf) free(tempbuf);
294	if(fs) fclose(fs);
295	return retcode;
296}
297
298int savebmp(char *filename, unsigned char *buf, int w, int h,
299	enum BMPPIXELFORMAT f, int srcpitch, int srcbottomup)
300{
301	int fd=-1, byteswritten, dstpitch, retcode=0;
302	int flags=O_RDWR|O_CREAT|O_TRUNC;
303	unsigned char *tempbuf=NULL;  char *temp;
304	bmphdr bh;  int mode;
305
306	#ifdef _WIN32
307	flags|=O_BINARY;  mode=_S_IREAD|_S_IWRITE;
308	#else
309	mode=S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
310	#endif
311	if(!filename || !buf || w<1 || h<1 || f<0 || f>BMPPIXELFORMATS-1 || srcpitch<0)
312		_throw("bad argument to savebmp()");
313
314	if(srcpitch==0) srcpitch=w*ps[f];
315
316	if((temp=strrchr(filename, '.'))!=NULL)
317	{
318		if(!strcasecmp(temp, ".ppm"))
319			return saveppm(filename, buf, w, h, f, srcpitch, srcbottomup);
320	}
321
322	_unix(fd=open(filename, flags, mode));
323	dstpitch=((w*3)+3)&(~3);
324
325	bh.bfType=0x4d42;
326	bh.bfSize=BMPHDRSIZE+dstpitch*h;
327	bh.bfReserved1=0;  bh.bfReserved2=0;
328	bh.bfOffBits=BMPHDRSIZE;
329	bh.biSize=40;
330	bh.biWidth=w;  bh.biHeight=h;
331	bh.biPlanes=0;  bh.biBitCount=24;
332	bh.biCompression=BI_RGB;  bh.biSizeImage=0;
333	bh.biXPelsPerMeter=0;  bh.biYPelsPerMeter=0;
334	bh.biClrUsed=0;  bh.biClrImportant=0;
335
336	if(!littleendian())
337	{
338		bh.bfType=byteswap16(bh.bfType);
339		bh.bfSize=byteswap(bh.bfSize);
340		bh.bfOffBits=byteswap(bh.bfOffBits);
341		bh.biSize=byteswap(bh.biSize);
342		bh.biWidth=byteswap(bh.biWidth);
343		bh.biHeight=byteswap(bh.biHeight);
344		bh.biPlanes=byteswap16(bh.biPlanes);
345		bh.biBitCount=byteswap16(bh.biBitCount);
346		bh.biCompression=byteswap(bh.biCompression);
347		bh.biSizeImage=byteswap(bh.biSizeImage);
348		bh.biXPelsPerMeter=byteswap(bh.biXPelsPerMeter);
349		bh.biYPelsPerMeter=byteswap(bh.biYPelsPerMeter);
350		bh.biClrUsed=byteswap(bh.biClrUsed);
351		bh.biClrImportant=byteswap(bh.biClrImportant);
352	}
353
354	writeme(fd, &bh.bfType, sizeof(unsigned short));
355	writeme(fd, &bh.bfSize, sizeof(unsigned int));
356	writeme(fd, &bh.bfReserved1, sizeof(unsigned short));
357	writeme(fd, &bh.bfReserved2, sizeof(unsigned short));
358	writeme(fd, &bh.bfOffBits, sizeof(unsigned int));
359	writeme(fd, &bh.biSize, sizeof(unsigned int));
360	writeme(fd, &bh.biWidth, sizeof(int));
361	writeme(fd, &bh.biHeight, sizeof(int));
362	writeme(fd, &bh.biPlanes, sizeof(unsigned short));
363	writeme(fd, &bh.biBitCount, sizeof(unsigned short));
364	writeme(fd, &bh.biCompression, sizeof(unsigned int));
365	writeme(fd, &bh.biSizeImage, sizeof(unsigned int));
366	writeme(fd, &bh.biXPelsPerMeter, sizeof(int));
367	writeme(fd, &bh.biYPelsPerMeter, sizeof(int));
368	writeme(fd, &bh.biClrUsed, sizeof(unsigned int));
369	writeme(fd, &bh.biClrImportant, sizeof(unsigned int));
370
371	if((tempbuf=(unsigned char *)malloc(dstpitch*h))==NULL)
372		_throw("Memory allocation error");
373
374	pixelconvert(buf, f, srcpitch, tempbuf, BMP_BGR, dstpitch, w, h,
375		!srcbottomup);
376
377	if((byteswritten=write(fd, tempbuf, dstpitch*h))!=dstpitch*h)
378		_throw(strerror(errno));
379
380	finally:
381	if(tempbuf) free(tempbuf);
382	if(fd!=-1) close(fd);
383	return retcode;
384}
385
386const char *bmpgeterr(void)
387{
388	return __bmperr;
389}
390