aboutsummaryrefslogtreecommitdiff
path: root/smelt/sdl/CxImage/ximamng.cpp
blob: d5ea82ef19432cd66f061b997c1f7047ab50e4b1 (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
/*
 * File:	ximamng.cpp
 * Purpose:	Platform Independent MNG Image Class Loader and Writer
 * Author:	07/Aug/2001 Davide Pizzolato - www.xdp.it
 * CxImage version 7.0.0 31/Dec/2010
 */

#include "ximamng.h"

#if CXIMAGE_SUPPORT_MNG

////////////////////////////////////////////////////////////////////////////////
// callbacks for the mng decoder:
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// memory allocation; data must be zeroed
static mng_ptr
mymngalloc( mng_size_t size )
{
	return (mng_ptr)calloc(1, size);
}

////////////////////////////////////////////////////////////////////////////////
// memory deallocation
static void mymngfree(mng_ptr p, mng_size_t size)
{
	free(p);
}

////////////////////////////////////////////////////////////////////////////////
// Stream open/close:
// since the user is responsible for opening and closing the file,
// we leave the default implementation open
static mng_bool mymngopenstream(mng_handle mng)      { return MNG_TRUE; }
static mng_bool mymngopenstreamwrite(mng_handle mng) { return MNG_TRUE; }
static mng_bool mymngclosestream(mng_handle mng)     { return MNG_TRUE; }

////////////////////////////////////////////////////////////////////////////////
// feed data to the decoder
static mng_bool mymngreadstream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32 *bytesread)
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	// read the requested amount of data from the file
	*bytesread = mymng->file->Read( buffer, sizeof(uint8_t), size);
	return MNG_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
static mng_bool mymngwritestream (mng_handle mng, mng_ptr pBuf, mng_uint32 iSize, mng_uint32 *iWritten)
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	// write it
	*iWritten = mymng->file->Write (pBuf, 1, iSize);
	return MNG_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// the header's been read. set up the display stuff
static mng_bool mymngprocessheader( mng_handle mng, mng_uint32 width, mng_uint32 height )
{
	// normally the image buffer is allocated here,
	// but in this module we don't know nothing about
	// the final environment.

	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	
	mymng->width  = width;
	mymng->height = height;
	mymng->bpp    = 24;
	mymng->effwdt = ((((width * mymng->bpp) + 31) >> 5) << 2);

	if (mng->bUseBKGD){
		mymng->nBkgndIndex = 0;
		mymng->nBkgndColor.rgbRed  = mng->iBGred >> 8;
		mymng->nBkgndColor.rgbGreen =mng->iBGgreen >> 8;
		mymng->nBkgndColor.rgbBlue = mng->iBGblue >> 8;
	}

	mymng->image = (uint8_t*)malloc(height * mymng->effwdt);

	// tell the mng decoder about our bit-depth choice
#if CXIMAGE_SUPPORT_ALPHA
	mng_set_canvasstyle( mng, MNG_CANVAS_RGB8_A8 );
	mymng->alpha = (uint8_t*)malloc(height * width);
#else
	mng_set_canvasstyle( mng, MNG_CANVAS_BGR8);
	mymng->alpha = NULL;
#endif
	return MNG_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// return a row pointer for the decoder to fill
static mng_ptr mymnggetcanvasline( mng_handle mng, mng_uint32 line )
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	return (mng_ptr)(mymng->image + (mymng->effwdt * (mymng->height - 1 - line)));
}
////////////////////////////////////////////////////////////////////////////////
// return a row pointer for the decoder to fill for alpha channel
static mng_ptr mymnggetalphaline( mng_handle mng, mng_uint32 line )
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	return (mng_ptr)(mymng->alpha + (mymng->width * (mymng->height - 1 - line)));
}

////////////////////////////////////////////////////////////////////////////////
// timer
static mng_uint32 mymnggetticks(mng_handle mng)
{
#ifdef WIN32
	return (mng_uint32)GetTickCount();
#else
  return 0;
#endif
}

////////////////////////////////////////////////////////////////////////////////
// Refresh: actual frame need to be updated (Invalidate)
static mng_bool mymngrefresh(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h)
{
//	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	return MNG_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// interframe delay callback
static mng_bool mymngsettimer(mng_handle mng, mng_uint32 msecs)
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
	mymng->delay = msecs; 	// set the timer for when the decoder wants to be woken
	return MNG_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
static mng_bool mymngerror(mng_handle mng, mng_int32 code, mng_int8 severity, mng_chunkid chunktype, mng_uint32 chunkseq, mng_int32 extra1, mng_int32 extra2, mng_pchar text)
{
	return mng_cleanup(&mng); //<Arkadiy Olovyannikov>
}

////////////////////////////////////////////////////////////////////////////////
// CxImage members
////////////////////////////////////////////////////////////////////////////////
CxImageMNG::CxImageMNG(): CxImage(CXIMAGE_FORMAT_MNG)
{
	hmng = NULL;
	memset(&mnginfo,0,sizeof(mngstuff));
	mnginfo.nBkgndIndex = -1;
	mnginfo.speed = 1.0f;
}
////////////////////////////////////////////////////////////////////////////////
CxImageMNG::~CxImageMNG()
{
	// cleanup and return
	if (mnginfo.thread){ //close the animation thread
		mnginfo.animation_enabled=0;
		ResumeThread(mnginfo.thread);
		WaitForSingleObject(mnginfo.thread,500);
		CloseHandle(mnginfo.thread);
	}
	// free objects
	if (mnginfo.image) free(mnginfo.image);
	if (mnginfo.alpha) free(mnginfo.alpha);
	if (hmng) mng_cleanup(&hmng); //be sure it's not needed any more. (active timers ?)
}
////////////////////////////////////////////////////////////////////////////////
void CxImageMNG::SetCallbacks(mng_handle mng)
{
	// set the callbacks
	mng_setcb_errorproc(mng, mymngerror);
	mng_setcb_openstream(mng, mymngopenstream);
	mng_setcb_closestream(mng, mymngclosestream);
	mng_setcb_readdata(mng, mymngreadstream);
	mng_setcb_processheader(mng, mymngprocessheader);
	mng_setcb_getcanvasline(mng, mymnggetcanvasline);
	mng_setcb_refresh(mng, mymngrefresh);
	mng_setcb_gettickcount(mng, mymnggetticks);
	mng_setcb_settimer(mng, mymngsettimer);
	mng_setcb_refresh(mng, mymngrefresh);
	mng_setcb_getalphaline(mng, mymnggetalphaline);
}
////////////////////////////////////////////////////////////////////////////////
// can't use the CxImage implementation because it looses mnginfo
bool CxImageMNG::Load(const TCHAR * imageFileName){
	FILE* hFile;	//file handle to read the image
#ifdef WIN32
	if ((hFile=_tfopen(imageFileName,_T("rb")))==NULL)  return false;	// For UNICODE support
#else
	if ((hFile=fopen(imageFileName,"rb"))==NULL)  return false;
#endif
	bool bOK = Decode(hFile);
	fclose(hFile);
	return bOK;
}
////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_DECODE
////////////////////////////////////////////////////////////////////////////////
bool CxImageMNG::Decode(CxFile *hFile)
{
	if (hFile == NULL) return false;

	cx_try
	{
		// set up the mng decoder for our stream
		hmng = mng_initialize(&mnginfo, (mng_memalloc)mymngalloc, (mng_memfree)mymngfree, MNG_NULL);
		if (hmng == NULL) cx_throw("could not initialize libmng");			

		// set the file we want to play
		mnginfo.file = hFile;

		// Set the colorprofile, lcms uses this:
		mng_set_srgb(hmng, MNG_TRUE );
		// Set white as background color:
		uint16_t Red,Green,Blue;
		Red = Green = Blue = (255 << 8) + 255;
		mng_set_bgcolor(hmng, Red, Green, Blue );
		// If PNG Background is available, use it:
		mng_set_usebkgd(hmng, MNG_TRUE );

		// No need to store chunks:
		mng_set_storechunks(hmng, MNG_FALSE);
		// No need to wait: straight reading
		mng_set_suspensionmode(hmng, MNG_FALSE);

		SetCallbacks(hmng);

		mng_datap pData = (mng_datap)hmng;

		// read in the image
		info.nNumFrames=0;
		int32_t retval=MNG_NOERROR;

		retval = mng_readdisplay(hmng);

		if (retval != MNG_NOERROR && retval != MNG_NEEDTIMERWAIT){
			mng_store_error(hmng,retval,0,0);
			if (hmng->zErrortext){
				cx_throw(hmng->zErrortext);
			} else {
				cx_throw("Error in MNG file");
			}
		}

		if (info.nEscape == -1) {
			// Return output dimensions only
			head.biWidth = hmng->iWidth;
			head.biHeight = hmng->iHeight;
			info.dwType = CXIMAGE_FORMAT_MNG;
			return true;
		}

		// read all
		while(pData->bReading){
			retval = mng_display_resume(hmng);
			info.nNumFrames++;
		}

		// single frame check:
		if (retval != MNG_NEEDTIMERWAIT){
			info.nNumFrames--;
		} else {
			mnginfo.animation=1;
		}

		if (info.nNumFrames<=0) info.nNumFrames=1;

		if (mnginfo.animation_enabled==0){
			// select the frame
			if (info.nFrame>=0 && info.nFrame<info.nNumFrames){
				for (int32_t n=0;n<info.nFrame;n++) mng_display_resume(hmng);
			} else cx_throw("Error: frame not present in MNG file");
		}

		if (mnginfo.nBkgndIndex >= 0){
			info.nBkgndIndex = mnginfo.nBkgndIndex;
			info.nBkgndColor.rgbRed = mnginfo.nBkgndColor.rgbRed;
			info.nBkgndColor.rgbGreen = mnginfo.nBkgndColor.rgbGreen;
			info.nBkgndColor.rgbBlue = mnginfo.nBkgndColor.rgbBlue;
		}

		//store the newly created image
		if (Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG)){
			memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight);
#if CXIMAGE_SUPPORT_ALPHA
			SwapRGB2BGR();
			AlphaCreate();
			if(AlphaIsValid() && mnginfo.alpha){
				memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height);
			}
#endif
		} else cx_throw("CxImageMNG::Decode cannot create image");


	} cx_catch {
		if (strcmp(message,"")) strncpy(info.szLastError,message,255);
		return false;
	}
	return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_DECODE
////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_ENCODE
////////////////////////////////////////////////////////////////////////////////
bool CxImageMNG::Encode(CxFile *hFile)
{
	if (EncodeSafeCheck(hFile)) return false;

	cx_try
	{
		if (head.biClrUsed != 0) cx_throw("MNG encoder can save only RGB images");
		// set the file we want to play
		mnginfo.file = hFile;
		mnginfo.bpp = head.biBitCount;
		mnginfo.effwdt = info.dwEffWidth;
		mnginfo.height = head.biHeight;
		mnginfo.width =  head.biWidth;

		mnginfo.image = (uint8_t*)malloc(head.biSizeImage);
		if (mnginfo.image == NULL) cx_throw("could not allocate memory for MNG");
		memcpy(mnginfo.image,info.pImage, head.biSizeImage);

		// set up the mng decoder for our stream
		hmng = mng_initialize(&mnginfo, (mng_memalloc)mymngalloc, (mng_memfree)mymngfree, MNG_NULL);
		if (hmng == NULL) cx_throw("could not initialize libmng");			

		mng_setcb_openstream(hmng, mymngopenstreamwrite );
		mng_setcb_closestream(hmng, mymngclosestream);
		mng_setcb_writedata(hmng, mymngwritestream);

		// Write File:
   		mng_create(hmng);
		// Just a single Frame (save a normal PNG):
		WritePNG(hmng, 0, 1 );
		// Now write file:
		mng_write(hmng);

	} cx_catch {
		if (strcmp(message,"")) strncpy(info.szLastError,message,255);
		return false;
	}
	return true;
}
////////////////////////////////////////////////////////////////////////////////
// Writes a single PNG datastream
void CxImageMNG::WritePNG( mng_handle hMNG, int32_t Frame, int32_t FrameCount )
{
	mngstuff *mymng = (mngstuff *)mng_get_userdata(hMNG);
	
	int32_t OffsetX=0,OffsetY=0,OffsetW=mymng->width,OffsetH=mymng->height;

	uint8_t *tmpbuffer = new uint8_t[ (mymng->effwdt+1) * mymng->height];
	if( tmpbuffer == 0 ) return;

	// Write DEFI chunk.
	mng_putchunk_defi( hMNG, 0, 0, 0, MNG_TRUE, OffsetX, OffsetY, MNG_FALSE, 0, 0, 0, 0 );
 		 
	// Write Header:
	mng_putchunk_ihdr(
		hMNG, 
		OffsetW, OffsetH, 
		MNG_BITDEPTH_8, 
		MNG_COLORTYPE_RGB, 
		MNG_COMPRESSION_DEFLATE, 
		MNG_FILTER_ADAPTIVE, 
		MNG_INTERLACE_NONE 
	);

	// transfer data, add Filterbyte:
	for( int32_t Row=0; Row<OffsetH; Row++ ){
		// First Byte in each Scanline is Filterbyte: Currently 0 -> No Filter.
		tmpbuffer[Row*(mymng->effwdt+1)]=0; 
		// Copy the scanline: (reverse order)
		memcpy(tmpbuffer+Row*(mymng->effwdt+1)+1, 
			mymng->image+((OffsetH-1-(OffsetY+Row))*(mymng->effwdt))+OffsetX,mymng->effwdt);
		// swap red and blue components
		RGBtoBGR(tmpbuffer+Row*(mymng->effwdt+1)+1,mymng->effwdt);
	} 

	// Compress data with ZLib (Deflate):
	uint8_t *dstbuffer = new uint8_t[(mymng->effwdt+1)*OffsetH];
	if( dstbuffer == 0 ) return;
	uint32_t dstbufferSize=(mymng->effwdt+1)*OffsetH;

	// Compress data:
	if(Z_OK != compress2((Bytef *)dstbuffer,(ULONG *)&dstbufferSize,(const Bytef*)tmpbuffer,
						(ULONG) (mymng->effwdt+1)*OffsetH,9 )) return;

	// Write Data into MNG File:
	mng_putchunk_idat( hMNG, dstbufferSize, (mng_ptr*)dstbuffer);
	mng_putchunk_iend(hMNG);

	// Free the stuff:
	delete [] tmpbuffer;
	delete [] dstbuffer;
}
////////////////////////////////////////////////////////////////////////////////
int32_t CxImageMNG::Resume()
{
	if (MNG_NEEDTIMERWAIT == mng_display_resume(hmng)){
		if (info.pImage==NULL){
			Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG);
		}
		if (IsValid()){
			memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight);
#if CXIMAGE_SUPPORT_ALPHA
			SwapRGB2BGR();
			AlphaCreate();
			if(AlphaIsValid() && mnginfo.alpha){
				memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height);
			}
#endif
		}
	} else {
		mnginfo.animation_enabled = 0;
	}
	return mnginfo.animation_enabled;
}
////////////////////////////////////////////////////////////////////////////////
void CxImageMNG::SetSpeed(float speed)
{
	if (speed>10.0) mnginfo.speed = 10.0f;
	else if (speed<0.1) mnginfo.speed = 0.1f;
	else mnginfo.speed=speed;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_ENCODE
////////////////////////////////////////////////////////////////////////////////
#endif // CXIMAGE_SUPPORT_MNG