1 | /*
|
---|
2 | * File: ximamng.cpp
|
---|
3 | * Purpose: Platform Independent MNG Image Class Loader and Writer
|
---|
4 | * Author: 07/Aug/2001 Davide Pizzolato - www.xdp.it
|
---|
5 | * CxImage version 6.0.0 02/Feb/2008
|
---|
6 | */
|
---|
7 |
|
---|
8 | #include "ximamng.h"
|
---|
9 |
|
---|
10 | #if CXIMAGE_SUPPORT_MNG
|
---|
11 |
|
---|
12 | ////////////////////////////////////////////////////////////////////////////////
|
---|
13 | // callbacks for the mng decoder:
|
---|
14 | ////////////////////////////////////////////////////////////////////////////////
|
---|
15 |
|
---|
16 | ////////////////////////////////////////////////////////////////////////////////
|
---|
17 | // memory allocation; data must be zeroed
|
---|
18 | static mng_ptr
|
---|
19 | mymngalloc( mng_uint32 size )
|
---|
20 | {
|
---|
21 | return (mng_ptr)calloc(1, size);
|
---|
22 | }
|
---|
23 |
|
---|
24 | ////////////////////////////////////////////////////////////////////////////////
|
---|
25 | // memory deallocation
|
---|
26 | static void mymngfree(mng_ptr p, mng_uint32 size)
|
---|
27 | {
|
---|
28 | free(p);
|
---|
29 | }
|
---|
30 |
|
---|
31 | ////////////////////////////////////////////////////////////////////////////////
|
---|
32 | // Stream open/close:
|
---|
33 | // since the user is responsible for opening and closing the file,
|
---|
34 | // we leave the default implementation open
|
---|
35 | static mng_bool mymngopenstream(mng_handle mng) { return MNG_TRUE; }
|
---|
36 | static mng_bool mymngopenstreamwrite(mng_handle mng) { return MNG_TRUE; }
|
---|
37 | static mng_bool mymngclosestream(mng_handle mng) { return MNG_TRUE; }
|
---|
38 |
|
---|
39 | ////////////////////////////////////////////////////////////////////////////////
|
---|
40 | // feed data to the decoder
|
---|
41 | static mng_bool mymngreadstream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32 *bytesread)
|
---|
42 | {
|
---|
43 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
44 | // read the requested amount of data from the file
|
---|
45 | *bytesread = mymng->file->Read( buffer, sizeof(BYTE), size);
|
---|
46 | return MNG_TRUE;
|
---|
47 | }
|
---|
48 |
|
---|
49 | ////////////////////////////////////////////////////////////////////////////////
|
---|
50 | static mng_bool mymngwritestream (mng_handle mng, mng_ptr pBuf, mng_uint32 iSize, mng_uint32 *iWritten)
|
---|
51 | {
|
---|
52 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
53 | // write it
|
---|
54 | *iWritten = mymng->file->Write (pBuf, 1, iSize);
|
---|
55 | return MNG_TRUE;
|
---|
56 | }
|
---|
57 |
|
---|
58 | ////////////////////////////////////////////////////////////////////////////////
|
---|
59 | // the header's been read. set up the display stuff
|
---|
60 | static mng_bool mymngprocessheader( mng_handle mng, mng_uint32 width, mng_uint32 height )
|
---|
61 | {
|
---|
62 | // normally the image buffer is allocated here,
|
---|
63 | // but in this module we don't know nothing about
|
---|
64 | // the final environment.
|
---|
65 |
|
---|
66 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
67 |
|
---|
68 | mymng->width = width;
|
---|
69 | mymng->height = height;
|
---|
70 | mymng->bpp = 24;
|
---|
71 | mymng->effwdt = ((((width * mymng->bpp) + 31) >> 5) << 2);
|
---|
72 |
|
---|
73 | if (mng->bUseBKGD){
|
---|
74 | mymng->nBkgndIndex = 0;
|
---|
75 | mymng->nBkgndColor.rgbRed = mng->iBGred >> 8;
|
---|
76 | mymng->nBkgndColor.rgbGreen =mng->iBGgreen >> 8;
|
---|
77 | mymng->nBkgndColor.rgbBlue = mng->iBGblue >> 8;
|
---|
78 | }
|
---|
79 |
|
---|
80 | mymng->image = (BYTE*)malloc(height * mymng->effwdt);
|
---|
81 |
|
---|
82 | // tell the mng decoder about our bit-depth choice
|
---|
83 | #if CXIMAGE_SUPPORT_ALPHA
|
---|
84 | mng_set_canvasstyle( mng, MNG_CANVAS_RGB8_A8 );
|
---|
85 | mymng->alpha = (BYTE*)malloc(height * width);
|
---|
86 | #else
|
---|
87 | mng_set_canvasstyle( mng, MNG_CANVAS_BGR8);
|
---|
88 | mymng->alpha = NULL;
|
---|
89 | #endif
|
---|
90 | return MNG_TRUE;
|
---|
91 | }
|
---|
92 |
|
---|
93 | ////////////////////////////////////////////////////////////////////////////////
|
---|
94 | // return a row pointer for the decoder to fill
|
---|
95 | static mng_ptr mymnggetcanvasline( mng_handle mng, mng_uint32 line )
|
---|
96 | {
|
---|
97 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
98 | return (mng_ptr)(mymng->image + (mymng->effwdt * (mymng->height - 1 - line)));
|
---|
99 | }
|
---|
100 | ////////////////////////////////////////////////////////////////////////////////
|
---|
101 | // return a row pointer for the decoder to fill for alpha channel
|
---|
102 | static mng_ptr mymnggetalphaline( mng_handle mng, mng_uint32 line )
|
---|
103 | {
|
---|
104 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
105 | return (mng_ptr)(mymng->alpha + (mymng->width * (mymng->height - 1 - line)));
|
---|
106 | }
|
---|
107 |
|
---|
108 | ////////////////////////////////////////////////////////////////////////////////
|
---|
109 | // timer
|
---|
110 | static mng_uint32 mymnggetticks(mng_handle mng)
|
---|
111 | {
|
---|
112 | #ifdef WIN32
|
---|
113 | return (mng_uint32)GetTickCount();
|
---|
114 | #else
|
---|
115 | return 0;
|
---|
116 | #endif
|
---|
117 | }
|
---|
118 |
|
---|
119 | ////////////////////////////////////////////////////////////////////////////////
|
---|
120 | // Refresh: actual frame need to be updated (Invalidate)
|
---|
121 | static mng_bool mymngrefresh(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h)
|
---|
122 | {
|
---|
123 | // mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
124 | return MNG_TRUE;
|
---|
125 | }
|
---|
126 |
|
---|
127 | ////////////////////////////////////////////////////////////////////////////////
|
---|
128 | // interframe delay callback
|
---|
129 | static mng_bool mymngsettimer(mng_handle mng, mng_uint32 msecs)
|
---|
130 | {
|
---|
131 | mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
|
---|
132 | mymng->delay = msecs; // set the timer for when the decoder wants to be woken
|
---|
133 | return MNG_TRUE;
|
---|
134 | }
|
---|
135 |
|
---|
136 | ////////////////////////////////////////////////////////////////////////////////
|
---|
137 | 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)
|
---|
138 | {
|
---|
139 | return mng_cleanup(&mng); //<Arkadiy Olovyannikov>
|
---|
140 | }
|
---|
141 |
|
---|
142 | ////////////////////////////////////////////////////////////////////////////////
|
---|
143 | // CxImage members
|
---|
144 | ////////////////////////////////////////////////////////////////////////////////
|
---|
145 | CxImageMNG::CxImageMNG(): CxImage(CXIMAGE_FORMAT_MNG)
|
---|
146 | {
|
---|
147 | hmng = NULL;
|
---|
148 | memset(&mnginfo,0,sizeof(mngstuff));
|
---|
149 | mnginfo.nBkgndIndex = -1;
|
---|
150 | mnginfo.speed = 1.0f;
|
---|
151 | }
|
---|
152 | ////////////////////////////////////////////////////////////////////////////////
|
---|
153 | CxImageMNG::~CxImageMNG()
|
---|
154 | {
|
---|
155 | // cleanup and return
|
---|
156 | if (mnginfo.thread){ //close the animation thread
|
---|
157 | mnginfo.animation_enabled=0;
|
---|
158 | ResumeThread(mnginfo.thread);
|
---|
159 | WaitForSingleObject(mnginfo.thread,500);
|
---|
160 | CloseHandle(mnginfo.thread);
|
---|
161 | }
|
---|
162 | // free objects
|
---|
163 | if (mnginfo.image) free(mnginfo.image);
|
---|
164 | if (mnginfo.alpha) free(mnginfo.alpha);
|
---|
165 | if (hmng) mng_cleanup(&hmng); //be sure it's not needed any more. (active timers ?)
|
---|
166 | }
|
---|
167 | ////////////////////////////////////////////////////////////////////////////////
|
---|
168 | void CxImageMNG::SetCallbacks(mng_handle mng)
|
---|
169 | {
|
---|
170 | // set the callbacks
|
---|
171 | mng_setcb_errorproc(mng, mymngerror);
|
---|
172 | mng_setcb_openstream(mng, mymngopenstream);
|
---|
173 | mng_setcb_closestream(mng, mymngclosestream);
|
---|
174 | mng_setcb_readdata(mng, mymngreadstream);
|
---|
175 | mng_setcb_processheader(mng, mymngprocessheader);
|
---|
176 | mng_setcb_getcanvasline(mng, mymnggetcanvasline);
|
---|
177 | mng_setcb_refresh(mng, mymngrefresh);
|
---|
178 | mng_setcb_gettickcount(mng, mymnggetticks);
|
---|
179 | mng_setcb_settimer(mng, mymngsettimer);
|
---|
180 | mng_setcb_refresh(mng, mymngrefresh);
|
---|
181 | mng_setcb_getalphaline(mng, mymnggetalphaline);
|
---|
182 | }
|
---|
183 | ////////////////////////////////////////////////////////////////////////////////
|
---|
184 | // can't use the CxImage implementation because it looses mnginfo
|
---|
185 | bool CxImageMNG::Load(const TCHAR * imageFileName){
|
---|
186 | FILE* hFile; //file handle to read the image
|
---|
187 | #ifdef WIN32
|
---|
188 | if ((hFile=_tfopen(imageFileName,_T("rb")))==NULL) return false; // For UNICODE support
|
---|
189 | #else
|
---|
190 | if ((hFile=fopen(imageFileName,"rb"))==NULL) return false;
|
---|
191 | #endif
|
---|
192 | bool bOK = Decode(hFile);
|
---|
193 | fclose(hFile);
|
---|
194 | return bOK;
|
---|
195 | }
|
---|
196 | ////////////////////////////////////////////////////////////////////////////////
|
---|
197 | #if CXIMAGE_SUPPORT_DECODE
|
---|
198 | ////////////////////////////////////////////////////////////////////////////////
|
---|
199 | bool CxImageMNG::Decode(CxFile *hFile)
|
---|
200 | {
|
---|
201 | if (hFile == NULL) return false;
|
---|
202 |
|
---|
203 | cx_try
|
---|
204 | {
|
---|
205 | // set up the mng decoder for our stream
|
---|
206 | hmng = mng_initialize(&mnginfo, mymngalloc, mymngfree, MNG_NULL);
|
---|
207 | if (hmng == NULL) cx_throw("could not initialize libmng");
|
---|
208 |
|
---|
209 | // set the file we want to play
|
---|
210 | mnginfo.file = hFile;
|
---|
211 |
|
---|
212 | // Set the colorprofile, lcms uses this:
|
---|
213 | mng_set_srgb(hmng, MNG_TRUE );
|
---|
214 | // Set white as background color:
|
---|
215 | WORD Red,Green,Blue;
|
---|
216 | Red = Green = Blue = (255 << 8) + 255;
|
---|
217 | mng_set_bgcolor(hmng, Red, Green, Blue );
|
---|
218 | // If PNG Background is available, use it:
|
---|
219 | mng_set_usebkgd(hmng, MNG_TRUE );
|
---|
220 |
|
---|
221 | // No need to store chunks:
|
---|
222 | mng_set_storechunks(hmng, MNG_FALSE);
|
---|
223 | // No need to wait: straight reading
|
---|
224 | mng_set_suspensionmode(hmng, MNG_FALSE);
|
---|
225 |
|
---|
226 | SetCallbacks(hmng);
|
---|
227 |
|
---|
228 | mng_datap pData = (mng_datap)hmng;
|
---|
229 |
|
---|
230 | // read in the image
|
---|
231 | info.nNumFrames=0;
|
---|
232 | int retval=MNG_NOERROR;
|
---|
233 |
|
---|
234 | retval = mng_readdisplay(hmng);
|
---|
235 |
|
---|
236 | if (retval != MNG_NOERROR && retval != MNG_NEEDTIMERWAIT){
|
---|
237 | mng_store_error(hmng,retval,0,0);
|
---|
238 | if (hmng->zErrortext){
|
---|
239 | cx_throw(hmng->zErrortext);
|
---|
240 | } else {
|
---|
241 | cx_throw("Error in MNG file");
|
---|
242 | }
|
---|
243 | }
|
---|
244 |
|
---|
245 | if (info.nEscape == -1) {
|
---|
246 | // Return output dimensions only
|
---|
247 | head.biWidth = hmng->iWidth;
|
---|
248 | head.biHeight = hmng->iHeight;
|
---|
249 | info.dwType = CXIMAGE_FORMAT_MNG;
|
---|
250 | return true;
|
---|
251 | }
|
---|
252 |
|
---|
253 | // read all
|
---|
254 | while(pData->bReading){
|
---|
255 | retval = mng_display_resume(hmng);
|
---|
256 | info.nNumFrames++;
|
---|
257 | }
|
---|
258 |
|
---|
259 | // single frame check:
|
---|
260 | if (retval != MNG_NEEDTIMERWAIT){
|
---|
261 | info.nNumFrames--;
|
---|
262 | } else {
|
---|
263 | mnginfo.animation=1;
|
---|
264 | }
|
---|
265 |
|
---|
266 | if (info.nNumFrames<=0) info.nNumFrames=1;
|
---|
267 |
|
---|
268 | if (mnginfo.animation_enabled==0){
|
---|
269 | // select the frame
|
---|
270 | if (info.nFrame>=0 && info.nFrame<info.nNumFrames){
|
---|
271 | for (int n=0;n<info.nFrame;n++) mng_display_resume(hmng);
|
---|
272 | } else cx_throw("Error: frame not present in MNG file");
|
---|
273 | }
|
---|
274 |
|
---|
275 | if (mnginfo.nBkgndIndex >= 0){
|
---|
276 | info.nBkgndIndex = mnginfo.nBkgndIndex;
|
---|
277 | info.nBkgndColor.rgbRed = mnginfo.nBkgndColor.rgbRed;
|
---|
278 | info.nBkgndColor.rgbGreen = mnginfo.nBkgndColor.rgbGreen;
|
---|
279 | info.nBkgndColor.rgbBlue = mnginfo.nBkgndColor.rgbBlue;
|
---|
280 | }
|
---|
281 |
|
---|
282 | //store the newly created image
|
---|
283 | if (Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG)){
|
---|
284 | memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight);
|
---|
285 | #if CXIMAGE_SUPPORT_ALPHA
|
---|
286 | SwapRGB2BGR();
|
---|
287 | AlphaCreate();
|
---|
288 | if(AlphaIsValid() && mnginfo.alpha){
|
---|
289 | memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height);
|
---|
290 | }
|
---|
291 | #endif
|
---|
292 | } else cx_throw("CxImageMNG::Decode cannot create image");
|
---|
293 |
|
---|
294 |
|
---|
295 | } cx_catch {
|
---|
296 | if (strcmp(message,"")) strncpy(info.szLastError,message,255);
|
---|
297 | return false;
|
---|
298 | }
|
---|
299 | return true;
|
---|
300 | }
|
---|
301 | ////////////////////////////////////////////////////////////////////////////////
|
---|
302 | #endif //CXIMAGE_SUPPORT_DECODE
|
---|
303 | ////////////////////////////////////////////////////////////////////////////////
|
---|
304 | #if CXIMAGE_SUPPORT_ENCODE
|
---|
305 | ////////////////////////////////////////////////////////////////////////////////
|
---|
306 | bool CxImageMNG::Encode(CxFile *hFile)
|
---|
307 | {
|
---|
308 | if (EncodeSafeCheck(hFile)) return false;
|
---|
309 |
|
---|
310 | cx_try
|
---|
311 | {
|
---|
312 | if (head.biClrUsed != 0) cx_throw("MNG encoder can save only RGB images");
|
---|
313 | // set the file we want to play
|
---|
314 | mnginfo.file = hFile;
|
---|
315 | mnginfo.bpp = head.biBitCount;
|
---|
316 | mnginfo.effwdt = info.dwEffWidth;
|
---|
317 | mnginfo.height = head.biHeight;
|
---|
318 | mnginfo.width = head.biWidth;
|
---|
319 |
|
---|
320 | mnginfo.image = (BYTE*)malloc(head.biSizeImage);
|
---|
321 | if (mnginfo.image == NULL) cx_throw("could not allocate memory for MNG");
|
---|
322 | memcpy(mnginfo.image,info.pImage, head.biSizeImage);
|
---|
323 |
|
---|
324 | // set up the mng decoder for our stream
|
---|
325 | hmng = mng_initialize(&mnginfo, mymngalloc, mymngfree, MNG_NULL);
|
---|
326 | if (hmng == NULL) cx_throw("could not initialize libmng");
|
---|
327 |
|
---|
328 | mng_setcb_openstream(hmng, mymngopenstreamwrite );
|
---|
329 | mng_setcb_closestream(hmng, mymngclosestream);
|
---|
330 | mng_setcb_writedata(hmng, mymngwritestream);
|
---|
331 |
|
---|
332 | // Write File:
|
---|
333 | mng_create(hmng);
|
---|
334 | // Just a single Frame (save a normal PNG):
|
---|
335 | WritePNG(hmng, 0, 1 );
|
---|
336 | // Now write file:
|
---|
337 | mng_write(hmng);
|
---|
338 |
|
---|
339 | } cx_catch {
|
---|
340 | if (strcmp(message,"")) strncpy(info.szLastError,message,255);
|
---|
341 | return false;
|
---|
342 | }
|
---|
343 | return true;
|
---|
344 | }
|
---|
345 | ////////////////////////////////////////////////////////////////////////////////
|
---|
346 | // Writes a single PNG datastream
|
---|
347 | void CxImageMNG::WritePNG( mng_handle hMNG, int Frame, int FrameCount )
|
---|
348 | {
|
---|
349 | mngstuff *mymng = (mngstuff *)mng_get_userdata(hMNG);
|
---|
350 |
|
---|
351 | int OffsetX=0,OffsetY=0,OffsetW=mymng->width,OffsetH=mymng->height;
|
---|
352 |
|
---|
353 | BYTE *tmpbuffer = new BYTE[ (mymng->effwdt+1) * mymng->height];
|
---|
354 | if( tmpbuffer == 0 ) return;
|
---|
355 |
|
---|
356 | // Write DEFI chunk.
|
---|
357 | mng_putchunk_defi( hMNG, 0, 0, 0, MNG_TRUE, OffsetX, OffsetY, MNG_FALSE, 0, 0, 0, 0 );
|
---|
358 |
|
---|
359 | // Write Header:
|
---|
360 | mng_putchunk_ihdr(
|
---|
361 | hMNG,
|
---|
362 | OffsetW, OffsetH,
|
---|
363 | MNG_BITDEPTH_8,
|
---|
364 | MNG_COLORTYPE_RGB,
|
---|
365 | MNG_COMPRESSION_DEFLATE,
|
---|
366 | MNG_FILTER_ADAPTIVE,
|
---|
367 | MNG_INTERLACE_NONE
|
---|
368 | );
|
---|
369 |
|
---|
370 | // transfer data, add Filterbyte:
|
---|
371 | for( int Row=0; Row<OffsetH; Row++ ){
|
---|
372 | // First Byte in each Scanline is Filterbyte: Currently 0 -> No Filter.
|
---|
373 | tmpbuffer[Row*(mymng->effwdt+1)]=0;
|
---|
374 | // Copy the scanline: (reverse order)
|
---|
375 | memcpy(tmpbuffer+Row*(mymng->effwdt+1)+1,
|
---|
376 | mymng->image+((OffsetH-1-(OffsetY+Row))*(mymng->effwdt))+OffsetX,mymng->effwdt);
|
---|
377 | // swap red and blue components
|
---|
378 | RGBtoBGR(tmpbuffer+Row*(mymng->effwdt+1)+1,mymng->effwdt);
|
---|
379 | }
|
---|
380 |
|
---|
381 | // Compress data with ZLib (Deflate):
|
---|
382 | BYTE *dstbuffer = new BYTE[(mymng->effwdt+1)*OffsetH];
|
---|
383 | if( dstbuffer == 0 ) return;
|
---|
384 | DWORD dstbufferSize=(mymng->effwdt+1)*OffsetH;
|
---|
385 |
|
---|
386 | // Compress data:
|
---|
387 | if(Z_OK != compress2((Bytef *)dstbuffer,(ULONG *)&dstbufferSize,(const Bytef*)tmpbuffer,
|
---|
388 | (ULONG) (mymng->effwdt+1)*OffsetH,9 )) return;
|
---|
389 |
|
---|
390 | // Write Data into MNG File:
|
---|
391 | mng_putchunk_idat( hMNG, dstbufferSize, (mng_ptr*)dstbuffer);
|
---|
392 | mng_putchunk_iend(hMNG);
|
---|
393 |
|
---|
394 | // Free the stuff:
|
---|
395 | delete [] tmpbuffer;
|
---|
396 | delete [] dstbuffer;
|
---|
397 | }
|
---|
398 | ////////////////////////////////////////////////////////////////////////////////
|
---|
399 | long CxImageMNG::Resume()
|
---|
400 | {
|
---|
401 | if (MNG_NEEDTIMERWAIT == mng_display_resume(hmng)){
|
---|
402 | if (info.pImage==NULL){
|
---|
403 | Create(mnginfo.width,mnginfo.height,mnginfo.bpp, CXIMAGE_FORMAT_MNG);
|
---|
404 | }
|
---|
405 | if (IsValid()){
|
---|
406 | memcpy(GetBits(), mnginfo.image, info.dwEffWidth * head.biHeight);
|
---|
407 | #if CXIMAGE_SUPPORT_ALPHA
|
---|
408 | SwapRGB2BGR();
|
---|
409 | AlphaCreate();
|
---|
410 | if(AlphaIsValid() && mnginfo.alpha){
|
---|
411 | memcpy(AlphaGetPointer(),mnginfo.alpha,mnginfo.width * mnginfo.height);
|
---|
412 | }
|
---|
413 | #endif
|
---|
414 | }
|
---|
415 | } else {
|
---|
416 | mnginfo.animation_enabled = 0;
|
---|
417 | }
|
---|
418 | return mnginfo.animation_enabled;
|
---|
419 | }
|
---|
420 | ////////////////////////////////////////////////////////////////////////////////
|
---|
421 | void CxImageMNG::SetSpeed(float speed)
|
---|
422 | {
|
---|
423 | if (speed>10.0) mnginfo.speed = 10.0f;
|
---|
424 | else if (speed<0.1) mnginfo.speed = 0.1f;
|
---|
425 | else mnginfo.speed=speed;
|
---|
426 | }
|
---|
427 | ////////////////////////////////////////////////////////////////////////////////
|
---|
428 | #endif //CXIMAGE_SUPPORT_ENCODE
|
---|
429 | ////////////////////////////////////////////////////////////////////////////////
|
---|
430 | #endif // CXIMAGE_SUPPORT_MNG
|
---|