aboutsummaryrefslogtreecommitdiff
path: root/smelt/sdl/CxImage/ximabmp.cpp
blob: c173c7d83c0f7f3d9c58ddb878f736fd4c1b7ab5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
/*
 * File:	ximabmp.cpp
 * Purpose:	Platform Independent BMP Image Class Loader and Writer
 * 07/Aug/2001 Davide Pizzolato - www.xdp.it
 * CxImage version 7.0.0 31/Dec/2010
 */

#include "ximabmp.h"

#if CXIMAGE_SUPPORT_BMP

#include "ximaiter.h"

////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_ENCODE
////////////////////////////////////////////////////////////////////////////////
bool CxImageBMP::Encode(CxFile * hFile)
{

	if (EncodeSafeCheck(hFile)) return false;

	BITMAPFILEHEADER	hdr;

	hdr.bfType = 0x4d42;   // 'BM' WINDOWS_BITMAP_SIGNATURE
	hdr.bfSize = GetSize() + 14 /*sizeof(BITMAPFILEHEADER)*/;
	hdr.bfReserved1 = hdr.bfReserved2 = 0;
	hdr.bfOffBits = 14 /*sizeof(BITMAPFILEHEADER)*/ + head.biSize + GetPaletteSize();

	hdr.bfType = m_ntohs(hdr.bfType);
	hdr.bfSize = m_ntohl(hdr.bfSize);
	hdr.bfOffBits = m_ntohl(hdr.bfOffBits);

#if CXIMAGE_SUPPORT_ALPHA
	if (GetNumColors()==0 && AlphaIsValid()){

		BITMAPINFOHEADER  infohdr;
		memcpy(&infohdr,&head,sizeof(BITMAPINFOHEADER));
		infohdr.biCompression = BI_RGB;
		infohdr.biBitCount = 32;
		uint32_t dwEffWidth = ((((infohdr.biBitCount * infohdr.biWidth) + 31) / 32) * 4);
		infohdr.biSizeImage = dwEffWidth * infohdr.biHeight;

		hdr.bfSize = infohdr.biSize + infohdr.biSizeImage + 14 /*sizeof(BITMAPFILEHEADER)*/;

		hdr.bfSize = m_ntohl(hdr.bfSize);
		bihtoh(&infohdr);

		// Write the file header
		hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1);
		hFile->Write(&infohdr,sizeof(BITMAPINFOHEADER),1);
		 //and DIB+ALPHA interlaced
		uint8_t *srcalpha = AlphaGetPointer();
		for(int32_t y = 0; y < infohdr.biHeight; ++y){
			uint8_t *srcdib = GetBits(y);
			for(int32_t x = 0; x < infohdr.biWidth; ++x){
				hFile->Write(srcdib,3,1);
				hFile->Write(srcalpha,1,1);
				srcdib += 3;
				++srcalpha;
			}
		}

	} else
#endif //CXIMAGE_SUPPORT_ALPHA
	{
		// Write the file header
		hFile->Write(&hdr,min(14,sizeof(BITMAPFILEHEADER)),1);
		//copy attributes
		memcpy(pDib,&head,sizeof(BITMAPINFOHEADER));
		bihtoh((BITMAPINFOHEADER*)pDib);
		// Write the DIB header and the pixels
		hFile->Write(pDib,GetSize(),1);
		bihtoh((BITMAPINFOHEADER*)pDib);
	}
	return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_ENCODE
////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_DECODE
////////////////////////////////////////////////////////////////////////////////
bool CxImageBMP::Decode(CxFile * hFile)
{
	if (hFile == NULL) return false;

	BITMAPFILEHEADER   bf;
	uint32_t off = hFile->Tell(); //<CSC>
  cx_try {
	if (hFile->Read(&bf,min(14,sizeof(bf)),1)==0) cx_throw("Not a BMP");

	bf.bfSize = m_ntohl(bf.bfSize);
	bf.bfOffBits = m_ntohl(bf.bfOffBits);

    if (m_ntohs(bf.bfType) != BFT_BITMAP) { //do we have a RC HEADER?
        bf.bfOffBits = 0L;
        hFile->Seek(off,SEEK_SET);
    }

	BITMAPINFOHEADER bmpHeader;
	if (!DibReadBitmapInfo(hFile,&bmpHeader)); cx_throw("Error reading BMP info");
	uint32_t dwCompression=bmpHeader.biCompression;
	uint32_t dwBitCount=bmpHeader.biBitCount; //preserve for BI_BITFIELDS compression <Thomas Ernst>
	bool bIsOldBmp = bmpHeader.biSize == sizeof(BITMAPCOREHEADER);

	bool bTopDownDib = bmpHeader.biHeight<0; //<Flanders> check if it's a top-down bitmap
	if (bTopDownDib) bmpHeader.biHeight=-bmpHeader.biHeight;

	if (info.nEscape == -1) {
		// Return output dimensions only
		head.biWidth = bmpHeader.biWidth;
		head.biHeight = bmpHeader.biHeight;
		info.dwType = CXIMAGE_FORMAT_BMP;
		cx_throw("output dimensions returned");
	}

	if (!Create(bmpHeader.biWidth,bmpHeader.biHeight,bmpHeader.biBitCount,CXIMAGE_FORMAT_BMP))
		cx_throw("");

	SetXDPI((int32_t) floor(bmpHeader.biXPelsPerMeter * 254.0 / 10000.0 + 0.5));
	SetYDPI((int32_t) floor(bmpHeader.biYPelsPerMeter * 254.0 / 10000.0 + 0.5));

	if (info.nEscape) cx_throw("Cancelled"); // <vho> - cancel decoding

    RGBQUAD *pRgb = GetPalette();
    if (pRgb){
        if (bIsOldBmp){
             // convert a old color table (3 byte entries) to a new
             // color table (4 byte entries)
            hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBTRIPLE),1);
            for (int32_t i=DibNumColors(&head)-1; i>=0; i--){
                pRgb[i].rgbRed      = ((RGBTRIPLE *)pRgb)[i].rgbtRed;
                pRgb[i].rgbBlue     = ((RGBTRIPLE *)pRgb)[i].rgbtBlue;
                pRgb[i].rgbGreen    = ((RGBTRIPLE *)pRgb)[i].rgbtGreen;
                pRgb[i].rgbReserved = (uint8_t)0;
            }
        } else {
            hFile->Read((void*)pRgb,DibNumColors(&bmpHeader) * sizeof(RGBQUAD),1);
			//force rgbReserved=0, to avoid problems with some WinXp bitmaps
			for (uint32_t i=0; i<head.biClrUsed; i++) pRgb[i].rgbReserved=0;
        }
    }

	if (info.nEscape) cx_throw("Cancelled"); // <vho> - cancel decoding

	switch (dwBitCount) {
		case 32 :
			uint32_t bfmask[3];
			if (dwCompression == BI_BITFIELDS)
			{
				hFile->Read(bfmask, 12, 1);
			} else {
				bfmask[0]=0x00FF0000;
				bfmask[1]=0x0000FF00;
				bfmask[2]=0x000000FF;
			}
			if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET);
			if (dwCompression == BI_BITFIELDS || dwCompression == BI_RGB){
				int32_t imagesize=4*head.biHeight*head.biWidth;
				uint8_t* buff32=(uint8_t*)malloc(imagesize);
				if (buff32){
					hFile->Read(buff32, imagesize,1); // read in the pixels

#if CXIMAGE_SUPPORT_ALPHA
					if (dwCompression == BI_RGB){
						AlphaCreate();
						if (AlphaIsValid()){
							bool bAlphaOk = false;
							uint8_t* p;
							for (int32_t y=0; y<head.biHeight; y++){
								p = buff32 + 3 + head.biWidth * 4 * y;
								for (int32_t x=0; x<head.biWidth; x++){
									if (*p) bAlphaOk = true;
									AlphaSet(x,y,*p);
									p+=4;
								}
							}
							// fix if alpha pixels are all zero
							if (!bAlphaOk) AlphaInvert();
						}
					}
#endif //CXIMAGE_SUPPORT_ALPHA

					Bitfield2RGB(buff32,bfmask[0],bfmask[1],bfmask[2],32);
					free(buff32);
				} else cx_throw("can't allocate memory");
			} else cx_throw("unknown compression");
			break;
		case 24 :
			if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET);
			if (dwCompression == BI_RGB){
				hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels
			} else cx_throw("unknown compression");
			break;
		case 16 :
		{
			uint32_t bfmask[3];
			if (dwCompression == BI_BITFIELDS)
			{
				hFile->Read(bfmask, 12, 1);
			} else {
				bfmask[0]=0x7C00; bfmask[1]=0x3E0; bfmask[2]=0x1F; //RGB555
			}
			// bf.bfOffBits required after the bitfield mask <Cui Ying Jie>
			if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET);
			// read in the pixels
			hFile->Read(info.pImage, head.biHeight*((head.biWidth+1)/2)*4,1);
			// transform into RGB
			Bitfield2RGB(info.pImage,bfmask[0],bfmask[1],bfmask[2],16);
			break;
		}
		case 8 :
		case 4 :
		case 1 :
		if (bf.bfOffBits != 0L) hFile->Seek(off + bf.bfOffBits,SEEK_SET);
		switch (dwCompression) {
			case BI_RGB :
				hFile->Read(info.pImage, head.biSizeImage,1); // read in the pixels
				break;
			case BI_RLE4 :
			{
				uint8_t status_byte = 0;
				uint8_t second_byte = 0;
				int32_t scanline = 0;
				int32_t bits = 0;
				BOOL low_nibble = FALSE;
				CImageIterator iter(this);

				for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(uint8_t), 1);) {

					switch (status_byte) {
						case RLE_COMMAND :
							hFile->Read(&status_byte, sizeof(uint8_t), 1);
							switch (status_byte) {
								case RLE_ENDOFLINE :
									bits = 0;
									scanline++;
									low_nibble = FALSE;
									break;
								case RLE_ENDOFBITMAP :
									bContinue=FALSE;
									break;
								case RLE_DELTA :
								{
									// read the delta values
									uint8_t delta_x;
									uint8_t delta_y;
									hFile->Read(&delta_x, sizeof(uint8_t), 1);
									hFile->Read(&delta_y, sizeof(uint8_t), 1);
									// apply them
									bits       += delta_x / 2;
									scanline   += delta_y;
									break;
								}
								default :
									hFile->Read(&second_byte, sizeof(uint8_t), 1);
									uint8_t *sline = iter.GetRow(scanline);
									for (int32_t i = 0; i < status_byte; i++) {
										if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){
											if (low_nibble) {
												if (i&1)
													*(sline + bits) |= (second_byte & 0x0f);
												else
													*(sline + bits) |= (second_byte & 0xf0)>>4;
												bits++;
											} else {
												if (i&1)
													*(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4;
												else
													*(sline + bits) = (uint8_t)(second_byte & 0xf0);
											}
										}

										if ((i & 1) && (i != (status_byte - 1)))
											hFile->Read(&second_byte, sizeof(uint8_t), 1);

										low_nibble = !low_nibble;
									}
									if ((((status_byte+1) >> 1) & 1 ) == 1)
										hFile->Read(&second_byte, sizeof(uint8_t), 1);
									break;
							};
							break;
						default :
						{
							uint8_t *sline = iter.GetRow(scanline);
							hFile->Read(&second_byte, sizeof(uint8_t), 1);
							for (unsigned i = 0; i < status_byte; i++) {
								if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){
									if (low_nibble) {
										if (i&1)
											*(sline + bits) |= (second_byte & 0x0f);
										else
											*(sline + bits) |= (second_byte & 0xf0)>>4;
										bits++;
									} else {
										if (i&1)
											*(sline + bits) = (uint8_t)(second_byte & 0x0f)<<4;
										else
											*(sline + bits) = (uint8_t)(second_byte & 0xf0);
									}
								}
								low_nibble = !low_nibble;
							}
						}
						break;
					};
				}
				break;
			}
			case BI_RLE8 :
			{
				uint8_t status_byte = 0;
				uint8_t second_byte = 0;
				int32_t scanline = 0;
				int32_t bits = 0;
				CImageIterator iter(this);

				for (BOOL bContinue = TRUE; bContinue && hFile->Read(&status_byte, sizeof(uint8_t), 1);) {
					switch (status_byte) {
						case RLE_COMMAND :
							hFile->Read(&status_byte, sizeof(uint8_t), 1);
							switch (status_byte) {
								case RLE_ENDOFLINE :
									bits = 0;
									scanline++;
									break;
								case RLE_ENDOFBITMAP :
									bContinue=FALSE;
									break;
								case RLE_DELTA :
								{
									// read the delta values
									uint8_t delta_x;
									uint8_t delta_y;
									hFile->Read(&delta_x, sizeof(uint8_t), 1);
									hFile->Read(&delta_y, sizeof(uint8_t), 1);
									// apply them
									bits     += delta_x;
									scanline += delta_y;
									break;
								}
								default :
									hFile->Read((void *)(iter.GetRow(scanline) + bits), sizeof(uint8_t) * status_byte, 1);
									// align run length to even number of bytes
									if ((status_byte & 1) == 1)
										hFile->Read(&second_byte, sizeof(uint8_t), 1);
									bits += status_byte;
									break;
							};
							break;
						default :
							uint8_t *sline = iter.GetRow(scanline);
							hFile->Read(&second_byte, sizeof(uint8_t), 1);
							for (unsigned i = 0; i < status_byte; i++) {
								if ((uint8_t*)(sline+bits) < (uint8_t*)(info.pImage+head.biSizeImage)){
									*(sline + bits) = second_byte;
									bits++;
								} else {
									break;
								}
							}
							break;
					};
				}
				break;
			}
			default :
				cx_throw("compression type not supported");
		}
	}

	if (bTopDownDib) Flip(); //<Flanders>

  } cx_catch {
	if (strcmp(message,"")) strncpy(info.szLastError,message,255);
	if (info.nEscape == -1 && info.dwType == CXIMAGE_FORMAT_BMP) return true;
	return false;
  }
    return true;
}
////////////////////////////////////////////////////////////////////////////////
/*  ReadDibBitmapInfo()
 *
 *  Will read a file in DIB format and return a global HANDLE to its
 *  BITMAPINFO.  This function will work with both "old" and "new"
 *  bitmap formats, but will always return a "new" BITMAPINFO.
 */
bool CxImageBMP::DibReadBitmapInfo(CxFile* fh, BITMAPINFOHEADER *pdib)
{
	if ((fh==NULL)||(pdib==NULL)) return false;

    if (fh->Read(pdib,sizeof(BITMAPINFOHEADER),1)==0) return false;

	bihtoh(pdib);

    switch (pdib->biSize) // what type of bitmap info is this?
    {
        case sizeof(BITMAPINFOHEADER):
            break;

		case 64: //sizeof(OS2_BMP_HEADER):
            fh->Seek((int32_t)(64 - sizeof(BITMAPINFOHEADER)),SEEK_CUR);
			break;

        case 124: //sizeof(BITMAPV5HEADER):
			fh->Seek((long)(124-sizeof(BITMAPINFOHEADER)), SEEK_CUR);
			break;

        case sizeof(BITMAPCOREHEADER):
		{
            BITMAPCOREHEADER bc = *(BITMAPCOREHEADER*)pdib;
            pdib->biSize               = bc.bcSize;
            pdib->biWidth              = (uint32_t)bc.bcWidth;
            pdib->biHeight             = (uint32_t)bc.bcHeight;
            pdib->biPlanes             =  bc.bcPlanes;
            pdib->biBitCount           =  bc.bcBitCount;
            pdib->biCompression        = BI_RGB;
            pdib->biSizeImage          = 0;
            pdib->biXPelsPerMeter      = 0;
            pdib->biYPelsPerMeter      = 0;
            pdib->biClrUsed            = 0;
            pdib->biClrImportant       = 0;

			fh->Seek((int32_t)(sizeof(BITMAPCOREHEADER)-sizeof(BITMAPINFOHEADER)), SEEK_CUR);
		}
            break;
        default:
			//give a last chance
			 if (pdib->biSize>(sizeof(BITMAPINFOHEADER))&&
				(pdib->biSizeImage>=(uint32_t)(pdib->biHeight*((((pdib->biBitCount*pdib->biWidth)+31)/32)*4)))&&
				(pdib->biPlanes==1)&&(pdib->biClrUsed==0))
			 {
	             if (pdib->biCompression==BI_RGB)
					 fh->Seek((int32_t)(pdib->biSize - sizeof(BITMAPINFOHEADER)),SEEK_CUR);
				 break;
			 }
			return false;
    }

    FixBitmapInfo(pdib);

    return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_DECODE
////////////////////////////////////////////////////////////////////////////////
#endif 	// CXIMAGE_SUPPORT_BMP
////////////////////////////////////////////////////////////////////////////////