1 // ==========================================================
2 // MNG / JNG helpers
3 //
4 // Design and implementation by
5 // - Herv� Drolon (drolon@infonie.fr)
6 //
7 // This file is part of FreeImage 3
8 //
9 // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 // THIS DISCLAIMER.
18 //
19 // Use at your own risk!
20 // ==========================================================
21 
22 #include "FreeImage.h"
23 #include "Utilities.h"
24 
25 /**
26 References
27 http://www.libpng.org/pub/mng/spec/jng.html
28 http://www.w3.org/TR/PNG/
29 http://libpng.org/pub/mng/spec/
30 */
31 
32 // --------------------------------------------------------------------------
33 
34 #define MNG_INCLUDE_JNG
35 
36 #ifdef MNG_INCLUDE_JNG
37 #define MNG_COLORTYPE_JPEGGRAY           8       /* JHDR */
38 #define MNG_COLORTYPE_JPEGCOLOR         10
39 #define MNG_COLORTYPE_JPEGGRAYA         12
40 #define MNG_COLORTYPE_JPEGCOLORA        14
41 
42 #define MNG_BITDEPTH_JPEG8               8       /* JHDR */
43 #define MNG_BITDEPTH_JPEG12             12
44 #define MNG_BITDEPTH_JPEG8AND12         20
45 
46 #define MNG_COMPRESSION_BASELINEJPEG     8       /* JHDR */
47 
48 #define MNG_INTERLACE_SEQUENTIAL         0       /* JHDR */
49 #define MNG_INTERLACE_PROGRESSIVE        8
50 #endif /* MNG_INCLUDE_JNG */
51 
52 // --------------------------------------------------------------------------
53 
54 #define JNG_SUPPORTED
55 
56 /** Size of a JDAT chunk on writing */
57 const DWORD JPEG_CHUNK_SIZE	= 8192;
58 
59 /** PNG signature */
60 static const BYTE g_png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
61 /** JNG signature */
62 static const BYTE g_jng_signature[8] = { 139, 74, 78, 71, 13, 10, 26, 10 };
63 
64 // --------------------------------------------------------------------------
65 
66 /** Chunk type converted to enum */
67 enum eChunckType {
68 	UNKNOWN_CHUNCK,
69 	MHDR,
70 	BACK,
71 	BASI,
72 	CLIP,
73 	CLON,
74 	DEFI,
75 	DHDR,
76 	DISC,
77 	ENDL,
78 	FRAM,
79 	IEND,
80 	IHDR,
81 	JHDR,
82 	LOOP,
83 	MAGN,
84 	MEND,
85 	MOVE,
86 	PAST,
87 	PLTE,
88 	SAVE,
89 	SEEK,
90 	SHOW,
91 	TERM,
92 	bKGD,
93 	cHRM,
94 	gAMA,
95 	iCCP,
96 	nEED,
97 	pHYg,
98 	vpAg,
99 	pHYs,
100 	sBIT,
101 	sRGB,
102 	tRNS,
103 	IDAT,
104 	JDAT,
105 	JDAA,
106 	JdAA,
107 	JSEP,
108 	oFFs,
109 	hIST,
110 	iTXt,
111 	sPLT,
112 	sTER,
113 	tEXt,
114 	tIME,
115 	zTXt
116 };
117 
118 /**
119 Helper for map<key, value> where value is a pointer to a string.
120 Used to store tEXt metadata.
121 */
122 typedef std::map<std::string, std::string> tEXtMAP;
123 
124 // --------------------------------------------------------------------------
125 
126 /*
127   Constant strings for known chunk types.  If you need to add a chunk,
128   add a string holding the name here.   To make the code more
129   portable, we use ASCII numbers like this, not characters.
130 */
131 
132 static BYTE mng_MHDR[5]={ 77,  72,  68,  82, (BYTE) '\0'};
133 static BYTE mng_BACK[5]={ 66,  65,  67,  75, (BYTE) '\0'};
134 static BYTE mng_BASI[5]={ 66,  65,  83,  73, (BYTE) '\0'};
135 static BYTE mng_CLIP[5]={ 67,  76,  73,  80, (BYTE) '\0'};
136 static BYTE mng_CLON[5]={ 67,  76,  79,  78, (BYTE) '\0'};
137 static BYTE mng_DEFI[5]={ 68,  69,  70,  73, (BYTE) '\0'};
138 static BYTE mng_DHDR[5]={ 68,  72,  68,  82, (BYTE) '\0'};
139 static BYTE mng_DISC[5]={ 68,  73,  83,  67, (BYTE) '\0'};
140 static BYTE mng_ENDL[5]={ 69,  78,  68,  76, (BYTE) '\0'};
141 static BYTE mng_FRAM[5]={ 70,  82,  65,  77, (BYTE) '\0'};
142 static BYTE mng_IEND[5]={ 73,  69,  78,  68, (BYTE) '\0'};
143 static BYTE mng_IHDR[5]={ 73,  72,  68,  82, (BYTE) '\0'};
144 static BYTE mng_JHDR[5]={ 74,  72,  68,  82, (BYTE) '\0'};
145 static BYTE mng_LOOP[5]={ 76,  79,  79,  80, (BYTE) '\0'};
146 static BYTE mng_MAGN[5]={ 77,  65,  71,  78, (BYTE) '\0'};
147 static BYTE mng_MEND[5]={ 77,  69,  78,  68, (BYTE) '\0'};
148 static BYTE mng_MOVE[5]={ 77,  79,  86,  69, (BYTE) '\0'};
149 static BYTE mng_PAST[5]={ 80,  65,  83,  84, (BYTE) '\0'};
150 static BYTE mng_PLTE[5]={ 80,  76,  84,  69, (BYTE) '\0'};
151 static BYTE mng_SAVE[5]={ 83,  65,  86,  69, (BYTE) '\0'};
152 static BYTE mng_SEEK[5]={ 83,  69,  69,  75, (BYTE) '\0'};
153 static BYTE mng_SHOW[5]={ 83,  72,  79,  87, (BYTE) '\0'};
154 static BYTE mng_TERM[5]={ 84,  69,  82,  77, (BYTE) '\0'};
155 static BYTE mng_bKGD[5]={ 98,  75,  71,  68, (BYTE) '\0'};
156 static BYTE mng_cHRM[5]={ 99,  72,  82,  77, (BYTE) '\0'};
157 static BYTE mng_gAMA[5]={103,  65,  77,  65, (BYTE) '\0'};
158 static BYTE mng_iCCP[5]={105,  67,  67,  80, (BYTE) '\0'};
159 static BYTE mng_nEED[5]={110,  69,  69,  68, (BYTE) '\0'};
160 static BYTE mng_pHYg[5]={112,  72,  89, 103, (BYTE) '\0'};
161 static BYTE mng_vpAg[5]={118, 112,  65, 103, (BYTE) '\0'};
162 static BYTE mng_pHYs[5]={112,  72,  89, 115, (BYTE) '\0'};
163 static BYTE mng_sBIT[5]={115,  66,  73,  84, (BYTE) '\0'};
164 static BYTE mng_sRGB[5]={115,  82,  71,  66, (BYTE) '\0'};
165 static BYTE mng_tRNS[5]={116,  82,  78,  83, (BYTE) '\0'};
166 
167 #if defined(JNG_SUPPORTED)
168 static BYTE mng_IDAT[5]={ 73,  68,  65,  84, (BYTE) '\0'};
169 static BYTE mng_JDAT[5]={ 74,  68,  65,  84, (BYTE) '\0'};
170 static BYTE mng_JDAA[5]={ 74,  68,  65,  65, (BYTE) '\0'};
171 static BYTE mng_JdAA[5]={ 74, 100,  65,  65, (BYTE) '\0'};
172 static BYTE mng_JSEP[5]={ 74,  83,  69,  80, (BYTE) '\0'};
173 static BYTE mng_oFFs[5]={111,  70,  70, 115, (BYTE) '\0'};
174 #endif
175 
176 static BYTE mng_hIST[5]={104,  73,  83,  84, (BYTE) '\0'};
177 static BYTE mng_iTXt[5]={105,  84,  88, 116, (BYTE) '\0'};
178 static BYTE mng_sPLT[5]={115,  80,  76,  84, (BYTE) '\0'};
179 static BYTE mng_sTER[5]={115,  84,  69,  82, (BYTE) '\0'};
180 static BYTE mng_tEXt[5]={116,  69,  88, 116, (BYTE) '\0'};
181 static BYTE mng_tIME[5]={116,  73,  77,  69, (BYTE) '\0'};
182 static BYTE mng_zTXt[5]={122,  84,  88, 116, (BYTE) '\0'};
183 
184 
185 // --------------------------------------------------------------------------
186 
187 /**
188 Convert a chunk name to a unique ID
189 */
190 static eChunckType
mng_GetChunckType(const BYTE * mChunkName)191 mng_GetChunckType(const BYTE *mChunkName) {
192 	if(memcmp(mChunkName, mng_MHDR, 4) == 0) {
193 		return MHDR;
194 	}
195 	if(memcmp(mChunkName, mng_LOOP, 4) == 0) {
196 		return LOOP;
197 	}
198 	if(memcmp(mChunkName, mng_DEFI, 4) == 0) {
199 		return DEFI;
200 	}
201 	if(memcmp(mChunkName, mng_PLTE, 4) == 0) {
202 		return PLTE;
203 	}
204 	if(memcmp(mChunkName, mng_tRNS, 4) == 0) {
205 		return tRNS;
206 	}
207 	if(memcmp(mChunkName, mng_IHDR, 4) == 0) {
208 		return IHDR;
209 	}
210 	if(memcmp(mChunkName, mng_JHDR, 4) == 0) {
211 		return JHDR;
212 	}
213 	if(memcmp(mChunkName, mng_MEND, 4) == 0) {
214 		return MEND;
215 	}
216 	if(memcmp(mChunkName, mng_IEND, 4) == 0) {
217 		return IEND;
218 	}
219 	if(memcmp(mChunkName, mng_JDAT, 4) == 0) {
220 		return JDAT;
221 	}
222 	if(memcmp(mChunkName, mng_IDAT, 4) == 0) {
223 		return IDAT;
224 	}
225 	if(memcmp(mChunkName, mng_JDAA, 4) == 0) {
226 		return JDAA;
227 	}
228 	if(memcmp(mChunkName, mng_gAMA, 4) == 0) {
229 		return gAMA;
230 	}
231 	if(memcmp(mChunkName, mng_pHYs, 4) == 0) {
232 		return pHYs;
233 	}
234 	if(memcmp(mChunkName, mng_bKGD, 4) == 0) {
235 		return bKGD;
236 	}
237 	if(memcmp(mChunkName, mng_tEXt, 4) == 0) {
238 		return tEXt;
239 	}
240 
241 	return UNKNOWN_CHUNCK;
242 }
243 
244 inline void
mng_SwapShort(WORD * sp)245 mng_SwapShort(WORD *sp) {
246 #ifndef FREEIMAGE_BIGENDIAN
247 	SwapShort(sp);
248 #endif
249 }
250 
251 inline void
mng_SwapLong(DWORD * lp)252 mng_SwapLong(DWORD *lp) {
253 #ifndef FREEIMAGE_BIGENDIAN
254 	SwapLong(lp);
255 #endif
256 }
257 
258 /**
259 Returns the size, in bytes, of a FreeImageIO stream, from the current position.
260 */
261 static long
mng_LOF(FreeImageIO * io,fi_handle handle)262 mng_LOF(FreeImageIO *io, fi_handle handle) {
263 	long start_pos = io->tell_proc(handle);
264 	io->seek_proc(handle, 0, SEEK_END);
265 	long file_length = io->tell_proc(handle);
266 	io->seek_proc(handle, start_pos, SEEK_SET);
267 	return file_length;
268 }
269 
270 /**
271 Count the number of bytes in a PNG stream, from IHDR to IEND.
272 If successful, the stream position, as given by io->tell_proc(handle),
273 should be the end of the PNG stream at the return of the function.
274 @param io
275 @param handle
276 @param inPos
277 @param m_TotalBytesOfChunks
278 @return Returns TRUE if successful, returns FALSE otherwise
279 */
280 static BOOL
mng_CountPNGChunks(FreeImageIO * io,fi_handle handle,long inPos,unsigned * m_TotalBytesOfChunks)281 mng_CountPNGChunks(FreeImageIO *io, fi_handle handle, long inPos, unsigned *m_TotalBytesOfChunks) {
282 	long mLOF;
283 	long mPos;
284 	BOOL mEnd = FALSE;
285 	DWORD mLength = 0;
286 	BYTE mChunkName[5];
287 
288 	*m_TotalBytesOfChunks = 0;
289 
290 	// get the length of the file
291 	mLOF = mng_LOF(io, handle);
292 
293 	// go to the start of the file
294 	io->seek_proc(handle, inPos, SEEK_SET);
295 
296 	try {
297 		// parse chunks
298 		while(mEnd == FALSE) {
299 			// chunk length
300 			mPos = io->tell_proc(handle);
301 			if(mPos + 4 > mLOF) {
302 				throw(1);
303 			}
304 			io->read_proc(&mLength, 1, 4, handle);
305 			mng_SwapLong(&mLength);
306 			// chunk name
307 			mPos = io->tell_proc(handle);
308 			if(mPos + 4 > mLOF) {
309 				throw(1);
310 			}
311 			io->read_proc(&mChunkName[0], 1, 4, handle);
312 			mChunkName[4] = '\0';
313 
314 			// go to next chunk
315 			mPos = io->tell_proc(handle);
316 			// 4 = size of the CRC
317 			if(mPos + (long)mLength + 4 > mLOF) {
318 				throw(1);
319 			}
320 			io->seek_proc(handle, mLength + 4, SEEK_CUR);
321 
322 			switch( mng_GetChunckType(mChunkName) ) {
323 				case IHDR:
324 					if(mLength != 13) {
325 						throw(1);
326 					}
327 					break;
328 
329 				case IEND:
330 					mEnd = TRUE;
331 					// the length below includes 4 bytes CRC, but no bytes for Length
332 					*m_TotalBytesOfChunks = io->tell_proc(handle) - inPos;
333 					break;
334 
335 				case UNKNOWN_CHUNCK:
336 				default:
337 					break;
338 			}
339 
340 		} // while(!mEnd)
341 
342 		return TRUE;
343 
344 	} catch(int) {
345 		return FALSE;
346 	}
347 }
348 
349 /**
350 Retrieve the position of a chunk in a PNG stream
351 @param hPngMemory PNG stream handle
352 @param chunk_name Name of the chunk to be found
353 @param offset Start of the search in the stream
354 @param start_pos [returned value] Start position of the chunk
355 @param next_pos [returned value] Start position of the next chunk
356 @return Returns TRUE if successful, returns FALSE otherwise
357 */
358 static BOOL
mng_FindChunk(FIMEMORY * hPngMemory,BYTE * chunk_name,long offset,DWORD * start_pos,DWORD * next_pos)359 mng_FindChunk(FIMEMORY *hPngMemory, BYTE *chunk_name, long offset, DWORD *start_pos, DWORD *next_pos) {
360 	DWORD mLength = 0;
361 
362 	BYTE *data = NULL;
363 	DWORD size_in_bytes = 0;
364 
365 	*start_pos = 0;
366 	*next_pos = 0;
367 
368 	// get a pointer to the stream buffer
369 	FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
370 	if(!(data && size_in_bytes) || (size_in_bytes < 20) || (size_in_bytes - offset < 20)) {
371 		// not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes)
372 		return FALSE;
373 	}
374 
375 	try {
376 
377 		// skip the signature and/or any following chunk(s)
378 		DWORD chunk_pos = offset;
379 
380 		while(1) {
381 			// get chunk length
382 			if(chunk_pos + 4 > size_in_bytes) {
383 				break;
384 			}
385 
386 			memcpy(&mLength, &data[chunk_pos], 4);
387 			mng_SwapLong(&mLength);
388 			chunk_pos += 4;
389 
390 			const DWORD next_chunk_pos = chunk_pos + 4 + mLength + 4;
391 			if(next_chunk_pos > size_in_bytes) {
392 				break;
393 			}
394 
395 			// get chunk name
396 			if(memcmp(&data[chunk_pos], chunk_name, 4) == 0) {
397 				chunk_pos -= 4;	// found chunk
398 				*start_pos = chunk_pos;
399 				*next_pos = next_chunk_pos;
400 				return TRUE;
401 			}
402 
403 			chunk_pos = next_chunk_pos;
404 		}
405 
406 		return FALSE;
407 
408 	} catch(int) {
409 		return FALSE;
410 	}
411 }
412 
413 /**
414 Remove a chunk located at (start_pos, next_pos) in the PNG stream
415 @param hPngMemory PNG stream handle
416 @param start_pos Start position of the chunk
417 @param next_pos Start position of the next chunk
418 @return Returns TRUE if successfull, returns FALSE otherwise
419 */
420 static BOOL
mng_CopyRemoveChunks(FIMEMORY * hPngMemory,DWORD start_pos,DWORD next_pos)421 mng_CopyRemoveChunks(FIMEMORY *hPngMemory, DWORD start_pos, DWORD next_pos) {
422 	BYTE *data = NULL;
423 	DWORD size_in_bytes = 0;
424 
425 	// length of the chunk to remove
426 	DWORD chunk_length = next_pos - start_pos;
427 	if(chunk_length == 0) {
428 		return TRUE;
429 	}
430 
431 	// get a pointer to the stream buffer
432 	FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
433 	if(!(data && size_in_bytes) || (size_in_bytes < 20) || (chunk_length >= size_in_bytes)) {
434 		// not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes)
435 		return FALSE;
436 	}
437 
438 	// new file length
439 	unsigned buffer_size = size_in_bytes + chunk_length;
440 
441 	BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE));
442 	if(!buffer) {
443 		return FALSE;
444 	}
445 	memcpy(&buffer[0], &data[0], start_pos);
446 	memcpy(&buffer[start_pos], &data[next_pos], size_in_bytes - next_pos);
447 
448 	// seek to the start of the stream
449 	FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
450 	// re-write the stream
451 	FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory);
452 
453 	free(buffer);
454 
455 	return TRUE;
456 }
457 
458 /**
459 Insert a chunk just before the inNextChunkName chunk
460 @param hPngMemory PNG stream handle
461 @param start_pos Start position of the inNextChunkName chunk
462 @param next_pos Start position of the next chunk
463 @return Returns TRUE if successfull, returns FALSE otherwise
464 */
465 static BOOL
mng_CopyInsertChunks(FIMEMORY * hPngMemory,BYTE * inNextChunkName,BYTE * inInsertChunk,DWORD inChunkLength,DWORD start_pos,DWORD next_pos)466 mng_CopyInsertChunks(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, DWORD inChunkLength, DWORD start_pos, DWORD next_pos) {
467 	BYTE *data = NULL;
468 	DWORD size_in_bytes = 0;
469 
470 	// length of the chunk to check
471 	DWORD chunk_length = next_pos - start_pos;
472 	if(chunk_length == 0) {
473 		return TRUE;
474 	}
475 
476 	// get a pointer to the stream buffer
477 	FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
478 	if(!(data && size_in_bytes) || (size_in_bytes < 20) || (chunk_length >= size_in_bytes)) {
479 		// not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes)
480 		return FALSE;
481 	}
482 
483 	// new file length
484 	unsigned buffer_size = inChunkLength + size_in_bytes;
485 
486 	BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE));
487 	if(!buffer) {
488 		return FALSE;
489 	}
490 	unsigned p = 0;
491 	memcpy(&buffer[p], &data[0], start_pos);
492 	p += start_pos;
493 	memcpy(&buffer[p], inInsertChunk, inChunkLength);
494 	p += inChunkLength;
495 	memcpy(&buffer[p], &data[start_pos], size_in_bytes - start_pos);
496 
497 	// seek to the start of the stream
498 	FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
499 	// re-write the stream
500 	FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory);
501 
502 	free(buffer);
503 
504 	return TRUE;
505 }
506 
507 static BOOL
mng_RemoveChunk(FIMEMORY * hPngMemory,BYTE * chunk_name)508 mng_RemoveChunk(FIMEMORY *hPngMemory, BYTE *chunk_name) {
509 	BOOL bResult = FALSE;
510 
511 	DWORD start_pos = 0;
512 	DWORD next_pos = 0;
513 
514 	bResult = mng_FindChunk(hPngMemory, chunk_name, 8, &start_pos, &next_pos);
515 	if(!bResult) {
516 		return FALSE;
517 	}
518 
519 	bResult = mng_CopyRemoveChunks(hPngMemory, start_pos, next_pos);
520 	if(!bResult) {
521 		return FALSE;
522 	}
523 
524 	return TRUE;
525 }
526 
527 static BOOL
mng_InsertChunk(FIMEMORY * hPngMemory,BYTE * inNextChunkName,BYTE * inInsertChunk,unsigned chunk_length)528 mng_InsertChunk(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, unsigned chunk_length) {
529 	BOOL bResult = FALSE;
530 
531 	DWORD start_pos = 0;
532 	DWORD next_pos = 0;
533 
534 	bResult = mng_FindChunk(hPngMemory, inNextChunkName, 8, &start_pos, &next_pos);
535 	if(!bResult) {
536 		return FALSE;
537 	}
538 
539 	bResult = mng_CopyInsertChunks(hPngMemory, inNextChunkName, inInsertChunk, chunk_length, start_pos, next_pos);
540 	if(!bResult) {
541 		return FALSE;
542 	}
543 
544 	return TRUE;
545 }
546 
547 static FIBITMAP*
mng_LoadFromMemoryHandle(FIMEMORY * hmem,int flags=0)548 mng_LoadFromMemoryHandle(FIMEMORY *hmem, int flags = 0) {
549 	long offset = 0;
550 	FIBITMAP *dib = NULL;
551 
552 	if(hmem) {
553 		// seek to the start of the stream
554 		FreeImage_SeekMemory(hmem, offset, SEEK_SET);
555 
556 		// check the file signature and deduce its format
557 		// (the second argument is currently not used by FreeImage)
558 		FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
559 		if(fif != FIF_UNKNOWN) {
560 			dib = FreeImage_LoadFromMemory(fif, hmem, flags);
561 		}
562 	}
563 
564 	return dib;
565 }
566 
567 /**
568 Write a chunk in a PNG stream from the current position.
569 @param chunk_name Name of the chunk
570 @param chunk_data Chunk array
571 @param length Chunk length
572 @param hPngMemory PNG stream handle
573 */
574 static void
mng_WriteChunk(BYTE * chunk_name,BYTE * chunk_data,DWORD length,FIMEMORY * hPngMemory)575 mng_WriteChunk(BYTE *chunk_name, BYTE *chunk_data, DWORD length, FIMEMORY *hPngMemory) {
576 	DWORD crc_file = 0;
577 	// write a PNG chunk ...
578 	// - length
579 	mng_SwapLong(&length);
580 	FreeImage_WriteMemory(&length, 1, 4, hPngMemory);
581 	mng_SwapLong(&length);
582 	// - chunk name
583 	FreeImage_WriteMemory(chunk_name, 1, 4, hPngMemory);
584 	if(chunk_data && length) {
585 		// - chunk data
586 		FreeImage_WriteMemory(chunk_data, 1, length, hPngMemory);
587 		// - crc
588 		crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4);
589 		crc_file = FreeImage_ZLibCRC32(crc_file, chunk_data, length);
590 		mng_SwapLong(&crc_file);
591 		FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory);
592 	} else {
593 		// - crc
594 		crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4);
595 		mng_SwapLong(&crc_file);
596 		FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory);
597 	}
598 
599 }
600 
601 /**
602 Wrap a IDAT chunk as a PNG stream.
603 The stream has the structure { g_png_signature, IHDR, IDAT, IEND }
604 The image is assumed to be a greyscale image.
605 
606 @param jng_width Image width
607 @param jng_height Image height
608 @param jng_alpha_sample_depth Bits per pixel
609 @param mChunk PNG grayscale IDAT format
610 @param mLength IDAT chunk length
611 @param hPngMemory Output memory stream
612 */
613 static void
mng_WritePNGStream(DWORD jng_width,DWORD jng_height,BYTE jng_alpha_sample_depth,BYTE * mChunk,DWORD mLength,FIMEMORY * hPngMemory)614 mng_WritePNGStream(DWORD jng_width, DWORD jng_height, BYTE jng_alpha_sample_depth, BYTE *mChunk, DWORD mLength, FIMEMORY *hPngMemory) {
615 	// PNG grayscale IDAT format
616 
617 	BYTE data[14];
618 
619 	// wrap the IDAT chunk as a PNG stream
620 
621 	// write PNG file signature
622 	FreeImage_WriteMemory(g_png_signature, 1, 8, hPngMemory);
623 
624 	// write a IHDR chunk ...
625 	/*
626 	The IHDR chunk must appear FIRST. It contains:
627 	Width:              4 bytes
628 	Height:             4 bytes
629 	Bit depth:          1 byte
630 	Color type:         1 byte
631 	Compression method: 1 byte
632 	Filter method:      1 byte
633 	Interlace method:   1 byte
634 	*/
635 	// - chunk data
636 	mng_SwapLong(&jng_width);
637 	mng_SwapLong(&jng_height);
638 	memcpy(&data[0], &jng_width, 4);
639 	memcpy(&data[4], &jng_height, 4);
640 	mng_SwapLong(&jng_width);
641 	mng_SwapLong(&jng_height);
642 	data[8] = jng_alpha_sample_depth;
643 	data[9] = 0;	// color_type gray (jng_color_type)
644 	data[10] = 0;	// compression method 0 (jng_alpha_compression_method)
645 	data[11] = 0;	// filter_method 0 (jng_alpha_filter_method)
646 	data[12] = 0;	// interlace_method 0 (jng_alpha_interlace_method)
647 
648 	mng_WriteChunk(mng_IHDR, &data[0], 13, hPngMemory);
649 
650 	// write a IDAT chunk ...
651 	mng_WriteChunk(mng_IDAT, mChunk, mLength, hPngMemory);
652 
653 	// write a IEND chunk ...
654 	mng_WriteChunk(mng_IEND, NULL, 0, hPngMemory);
655 
656 }
657 
658 // --------------------------------------------------------------------------
659 
660 /**
661 Build and set a FITAG whose type is FIDT_ASCII.
662 The tag must be destroyed by the caller using FreeImage_DeleteTag.
663 @param model Metadata model to be filled
664 @param dib Image to be filled
665 @param key Tag key
666 @param value Tag value
667 @return Returns TRUE if successful, returns FALSE otherwise
668 */
669 static BOOL
mng_SetKeyValue(FREE_IMAGE_MDMODEL model,FIBITMAP * dib,const char * key,const char * value)670 mng_SetKeyValue(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, const char *value) {
671 	if(!dib || !key || !value) {
672 		return FALSE;
673 	}
674 	// create a tag
675 	FITAG *tag = FreeImage_CreateTag();
676 	if(tag) {
677 		BOOL bSuccess = TRUE;
678 		// fill the tag
679 		DWORD tag_length = (DWORD)(strlen(value) + 1);
680 		bSuccess &= FreeImage_SetTagKey(tag, key);
681 		bSuccess &= FreeImage_SetTagLength(tag, tag_length);
682 		bSuccess &= FreeImage_SetTagCount(tag, tag_length);
683 		bSuccess &= FreeImage_SetTagType(tag, FIDT_ASCII);
684 		bSuccess &= FreeImage_SetTagValue(tag, value);
685 		if(bSuccess) {
686 			// set the tag
687 			FreeImage_SetMetadata(model, dib, FreeImage_GetTagKey(tag), tag);
688 		}
689 		FreeImage_DeleteTag(tag);
690 		return bSuccess;
691 	}
692 
693 	return FALSE;
694 }
695 
696 /**
697 Read a tEXt chunk and extract the key/value pair.
698 @param key_value_pair [returned value] Array of key/value pairs
699 @param mChunk Chunk data
700 @param mLength Chunk length
701 @return Returns TRUE if successful, returns FALSE otherwise
702 */
703 static BOOL
mng_SetMetadata_tEXt(tEXtMAP & key_value_pair,const BYTE * mChunk,DWORD mLength)704 mng_SetMetadata_tEXt(tEXtMAP &key_value_pair, const BYTE *mChunk, DWORD mLength) {
705 	std::string key;
706 	std::string value;
707 	BYTE *buffer = (BYTE*)malloc(mLength * sizeof(BYTE));
708 	if(!buffer) {
709 		return FALSE;
710 	}
711 	DWORD pos = 0;
712 
713 	memset(buffer, 0, mLength * sizeof(BYTE));
714 
715 	for(DWORD i = 0; i < mLength; i++) {
716 		buffer[pos++] = mChunk[i];
717 		if(mChunk[i] == '\0') {
718 			if(key.size() == 0) {
719 				key = (char*)buffer;
720 				pos = 0;
721 				memset(buffer, 0, mLength * sizeof(BYTE));
722 			} else {
723 				break;
724 			}
725 		}
726 	}
727 	value = (char*)buffer;
728 	free(buffer);
729 
730 	key_value_pair[key] = value;
731 
732 	return TRUE;
733 }
734 
735 // --------------------------------------------------------------------------
736 
737 /**
738 Load a FIBITMAP from a MNG or a JNG stream
739 @param format_id ID of the caller
740 @param io Stream i/o functions
741 @param handle Stream handle
742 @param Offset Start of the first chunk
743 @param flags Loading flags
744 @return Returns a dib if successful, returns NULL otherwise
745 */
746 FIBITMAP*
mng_ReadChunks(int format_id,FreeImageIO * io,fi_handle handle,long Offset,int flags=0)747 mng_ReadChunks(int format_id, FreeImageIO *io, fi_handle handle, long Offset, int flags = 0) {
748 	DWORD mLength = 0;
749 	BYTE mChunkName[5];
750 	BYTE *mChunk = NULL;
751 	DWORD crc_file;
752 	long LastOffset;
753 	long mOrigPos;
754 	BYTE *PLTE_file_chunk = NULL;	// whole PLTE chunk (lentgh, name, array, crc)
755 	DWORD PLTE_file_size = 0;		// size of PLTE chunk
756 
757 	BOOL m_HasGlobalPalette = FALSE; // may turn to TRUE in PLTE chunk
758 	unsigned m_TotalBytesOfChunks = 0;
759 	FIBITMAP *dib = NULL;
760 	FIBITMAP *dib_alpha = NULL;
761 
762 	FIMEMORY *hJpegMemory = NULL;
763 	FIMEMORY *hPngMemory = NULL;
764 	FIMEMORY *hIDATMemory = NULL;
765 
766 	// ---
767 	DWORD jng_width = 0;
768 	DWORD jng_height = 0;
769 	BYTE jng_color_type = 0;
770 	BYTE jng_image_sample_depth = 0;
771 	BYTE jng_image_compression_method = 0;
772 
773 	BYTE jng_alpha_sample_depth = 0;
774 	BYTE jng_alpha_compression_method = 0;
775 	BYTE jng_alpha_filter_method = 0;
776 	BYTE jng_alpha_interlace_method = 0;
777 
778 	DWORD mng_frame_width = 0;
779 	DWORD mng_frame_height = 0;
780 	DWORD mng_ticks_per_second = 0;
781 	DWORD mng_nominal_layer_count = 0;
782 	DWORD mng_nominal_frame_count = 0;
783 	DWORD mng_nominal_play_time = 0;
784 	DWORD mng_simplicity_profile = 0;
785 
786 
787 	DWORD res_x = 2835;	// 72 dpi
788 	DWORD res_y = 2835;	// 72 dpi
789 	RGBQUAD rgbBkColor = {0, 0, 0, 0};
790 	WORD bk_red, bk_green, bk_blue;
791 	BOOL hasBkColor = FALSE;
792 	BOOL mHasIDAT = FALSE;
793 
794 	tEXtMAP key_value_pair;
795 
796 	// ---
797 
798 	BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
799 
800 	// get the file size
801 	const long mLOF = mng_LOF(io, handle);
802 	// go to the first chunk
803 	io->seek_proc(handle, Offset, SEEK_SET);
804 
805 	try {
806 		BOOL mEnd = FALSE;
807 
808 		while(mEnd == FALSE) {
809 			// start of the chunk
810 			LastOffset = io->tell_proc(handle);
811 			// read length
812 			mLength = 0;
813 			io->read_proc(&mLength, 1, sizeof(mLength), handle);
814 			mng_SwapLong(&mLength);
815 			// read name
816 			io->read_proc(&mChunkName[0], 1, 4, handle);
817 			mChunkName[4] = '\0';
818 
819 			if(mLength > 0) {
820 				mChunk = (BYTE*)realloc(mChunk, mLength);
821 				if(!mChunk) {
822 					FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
823 					throw (const char*)NULL;
824 				}
825 				Offset = io->tell_proc(handle);
826 				if(Offset + (long)mLength > mLOF) {
827 					FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of file", mChunkName);
828 					throw (const char*)NULL;
829 				}
830 				// read chunk
831 				io->read_proc(mChunk, 1, mLength, handle);
832 			}
833 			// read crc
834 			io->read_proc(&crc_file, 1, sizeof(crc_file), handle);
835 			mng_SwapLong(&crc_file);
836 			// check crc
837 			DWORD crc_check = FreeImage_ZLibCRC32(0, &mChunkName[0], 4);
838 			crc_check = FreeImage_ZLibCRC32(crc_check, mChunk, mLength);
839 			if(crc_check != crc_file) {
840 				FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: bad CRC", mChunkName);
841 				throw (const char*)NULL;
842 			}
843 
844 			switch( mng_GetChunckType(mChunkName) ) {
845 				case MHDR:
846 					// The MHDR chunk is always first in all MNG datastreams except for those
847 					// that consist of a single PNG or JNG datastream with a PNG or JNG signature.
848 					if(mLength == 28) {
849 						memcpy(&mng_frame_width, &mChunk[0], 4);
850 						memcpy(&mng_frame_height, &mChunk[4], 4);
851 						memcpy(&mng_ticks_per_second, &mChunk[8], 4);
852 						memcpy(&mng_nominal_layer_count, &mChunk[12], 4);
853 						memcpy(&mng_nominal_frame_count, &mChunk[16], 4);
854 						memcpy(&mng_nominal_play_time, &mChunk[20], 4);
855 						memcpy(&mng_simplicity_profile, &mChunk[24], 4);
856 
857 						mng_SwapLong(&mng_frame_width);
858 						mng_SwapLong(&mng_frame_height);
859 						mng_SwapLong(&mng_ticks_per_second);
860 						mng_SwapLong(&mng_nominal_layer_count);
861 						mng_SwapLong(&mng_nominal_frame_count);
862 						mng_SwapLong(&mng_nominal_play_time);
863 						mng_SwapLong(&mng_simplicity_profile);
864 
865 					} else {
866 						FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: size is %d instead of 28", mChunkName, mLength);
867 					}
868 					break;
869 
870 				case MEND:
871 					mEnd = TRUE;
872 					break;
873 
874 				case LOOP:
875 				case ENDL:
876 					break;
877 				case DEFI:
878 					break;
879 				case SAVE:
880 				case SEEK:
881 				case TERM:
882 					break;
883 				case BACK:
884 					break;
885 
886 					// Global "PLTE" and "tRNS" (if any).  PNG "PLTE" will be of 0 byte, as it uses global data.
887 				case PLTE:	// Global
888 					m_HasGlobalPalette = TRUE;
889 					PLTE_file_size = mLength + 12; // (lentgh, name, array, crc) = (4, 4, mLength, 4)
890 					PLTE_file_chunk = (BYTE*)realloc(PLTE_file_chunk, PLTE_file_size);
891 					if(!PLTE_file_chunk) {
892 						FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
893 						throw (const char*)NULL;
894 					} else {
895 						mOrigPos = io->tell_proc(handle);
896 						// seek to the start of the chunk
897 						io->seek_proc(handle, LastOffset, SEEK_SET);
898 						// load the whole chunk
899 						io->read_proc(PLTE_file_chunk, 1, PLTE_file_size, handle);
900 						// go to the start of the next chunk
901 						io->seek_proc(handle, mOrigPos, SEEK_SET);
902 					}
903 					break;
904 
905 				case tRNS:	// Global
906 					break;
907 
908 				case IHDR:
909 					Offset = LastOffset;
910 					// parse the PNG file and get its file size
911 					if(mng_CountPNGChunks(io, handle, Offset, &m_TotalBytesOfChunks) == FALSE) {
912 						// reach an unexpected end of file
913 						mEnd = TRUE;
914 						FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of PNG file", mChunkName);
915 						break;
916 					}
917 
918 					// wrap the { IHDR, ..., IEND } chunks as a PNG stream
919 					if(hPngMemory == NULL) {
920 						hPngMemory = FreeImage_OpenMemory();
921 					}
922 
923 					mOrigPos = io->tell_proc(handle);
924 
925 					// write PNG file signature
926 					FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
927 					FreeImage_WriteMemory(g_png_signature, 1, 8, hPngMemory);
928 
929 					mChunk = (BYTE*)realloc(mChunk, m_TotalBytesOfChunks);
930 					if(!mChunk) {
931 						FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
932 						throw (const char*)NULL;
933 					}
934 
935 					// on calling CountPNGChunks earlier, we were in Offset pos,
936 					// go back there
937 					io->seek_proc(handle, Offset, SEEK_SET);
938 					io->read_proc(mChunk, 1, m_TotalBytesOfChunks, handle);
939 					// Put back to original pos
940 					io->seek_proc(handle, mOrigPos, SEEK_SET);
941 					// write the PNG chunks
942 					FreeImage_WriteMemory(mChunk, 1, m_TotalBytesOfChunks, hPngMemory);
943 
944 					// plug in global PLTE if local PLTE exists
945 					if(m_HasGlobalPalette) {
946 						// ensure we remove some local chunks, so that global
947 						// "PLTE" can be inserted right before "IDAT".
948 						mng_RemoveChunk(hPngMemory, mng_PLTE);
949 						mng_RemoveChunk(hPngMemory, mng_tRNS);
950 						mng_RemoveChunk(hPngMemory, mng_bKGD);
951 						// insert global "PLTE" chunk in its entirety before "IDAT"
952 						mng_InsertChunk(hPngMemory, mng_IDAT, PLTE_file_chunk, PLTE_file_size);
953 					}
954 
955 					if(dib) FreeImage_Unload(dib);
956 					dib = mng_LoadFromMemoryHandle(hPngMemory, flags);
957 
958 					// stop after the first image
959 					mEnd = TRUE;
960 					break;
961 
962 				case JHDR:
963 					if(mLength == 16) {
964 						memcpy(&jng_width, &mChunk[0], 4);
965 						memcpy(&jng_height, &mChunk[4], 4);
966 						mng_SwapLong(&jng_width);
967 						mng_SwapLong(&jng_height);
968 
969 						jng_color_type = mChunk[8];
970 						jng_image_sample_depth = mChunk[9];
971 						jng_image_compression_method = mChunk[10];
972 						//BYTE jng_image_interlace_method = mChunk[11];	// for debug only
973 
974 						jng_alpha_sample_depth = mChunk[12];
975 						jng_alpha_compression_method = mChunk[13];
976 						jng_alpha_filter_method = mChunk[14];
977 						jng_alpha_interlace_method = mChunk[15];
978 					} else {
979 						FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: invalid chunk length", mChunkName);
980 						throw (const char*)NULL;
981 					}
982 					break;
983 
984 				case JDAT:
985 					if(hJpegMemory == NULL) {
986 						hJpegMemory = FreeImage_OpenMemory();
987 					}
988 					// as there may be several JDAT chunks, concatenate them
989 					FreeImage_WriteMemory(mChunk, 1, mLength, hJpegMemory);
990 					break;
991 
992 				case IDAT:
993 					if(!header_only && (jng_alpha_compression_method == 0)) {
994 						// PNG grayscale IDAT format
995 						if(hIDATMemory == NULL) {
996 							hIDATMemory = FreeImage_OpenMemory();
997 							mHasIDAT = TRUE;
998 						}
999 						// as there may be several IDAT chunks, concatenate them
1000 						FreeImage_WriteMemory(mChunk, 1, mLength, hIDATMemory);
1001 					}
1002 					break;
1003 
1004 				case IEND:
1005 					if(!hJpegMemory) {
1006 						mEnd = TRUE;
1007 						break;
1008 					}
1009 					// load the JPEG
1010 					if(dib) {
1011 						FreeImage_Unload(dib);
1012 					}
1013 					dib = mng_LoadFromMemoryHandle(hJpegMemory, flags);
1014 
1015 					// load the PNG alpha layer
1016 					if(mHasIDAT) {
1017 						BYTE *data = NULL;
1018 						DWORD size_in_bytes = 0;
1019 
1020 						// get a pointer to the IDAT buffer
1021 						FreeImage_AcquireMemory(hIDATMemory, &data, &size_in_bytes);
1022 						if(data && size_in_bytes) {
1023 							// wrap the IDAT chunk as a PNG stream
1024 							if(hPngMemory == NULL) {
1025 								hPngMemory = FreeImage_OpenMemory();
1026 							}
1027 							mng_WritePNGStream(jng_width, jng_height, jng_alpha_sample_depth, data, size_in_bytes, hPngMemory);
1028 							// load the PNG
1029 							if(dib_alpha) {
1030 								FreeImage_Unload(dib_alpha);
1031 							}
1032 							dib_alpha = mng_LoadFromMemoryHandle(hPngMemory, flags);
1033 						}
1034 					}
1035 					// stop the parsing
1036 					mEnd = TRUE;
1037 					break;
1038 
1039 				case JDAA:
1040 					break;
1041 
1042 				case gAMA:
1043 					break;
1044 
1045 				case pHYs:
1046 					// unit is pixels per meter
1047 					memcpy(&res_x, &mChunk[0], 4);
1048 					mng_SwapLong(&res_x);
1049 					memcpy(&res_y, &mChunk[4], 4);
1050 					mng_SwapLong(&res_y);
1051 					break;
1052 
1053 				case bKGD:
1054 					memcpy(&bk_red, &mChunk[0], 2);
1055 					mng_SwapShort(&bk_red);
1056 					rgbBkColor.rgbRed = (BYTE)bk_red;
1057 					memcpy(&bk_green, &mChunk[2], 2);
1058 					mng_SwapShort(&bk_green);
1059 					rgbBkColor.rgbGreen = (BYTE)bk_green;
1060 					memcpy(&bk_blue, &mChunk[4], 2);
1061 					mng_SwapShort(&bk_blue);
1062 					rgbBkColor.rgbBlue = (BYTE)bk_blue;
1063 					hasBkColor = TRUE;
1064 					break;
1065 
1066 				case tEXt:
1067 					mng_SetMetadata_tEXt(key_value_pair, mChunk, mLength);
1068 					break;
1069 
1070 				case UNKNOWN_CHUNCK:
1071 				default:
1072 					break;
1073 
1074 
1075 			} // switch( GetChunckType )
1076 		} // while(!mEnd)
1077 
1078 		FreeImage_CloseMemory(hJpegMemory);
1079 		FreeImage_CloseMemory(hPngMemory);
1080 		FreeImage_CloseMemory(hIDATMemory);
1081 		free(mChunk);
1082 		free(PLTE_file_chunk);
1083 
1084 		// convert to 32-bit if a transparent layer is available
1085 		if(!header_only && dib_alpha) {
1086 			FIBITMAP *dst = FreeImage_ConvertTo32Bits(dib);
1087 			if((FreeImage_GetBPP(dib_alpha) == 8) && (FreeImage_GetImageType(dib_alpha) == FIT_BITMAP)) {
1088 				FreeImage_SetChannel(dst, dib_alpha, FICC_ALPHA);
1089 			} else {
1090 				FIBITMAP *dst_alpha = FreeImage_ConvertTo8Bits(dib_alpha);
1091 				FreeImage_SetChannel(dst, dst_alpha, FICC_ALPHA);
1092 				FreeImage_Unload(dst_alpha);
1093 			}
1094 			FreeImage_Unload(dib);
1095 			dib = dst;
1096 		}
1097 		FreeImage_Unload(dib_alpha);
1098 
1099 		if(dib) {
1100 			// set metadata
1101 			FreeImage_SetDotsPerMeterX(dib, res_x);
1102 			FreeImage_SetDotsPerMeterY(dib, res_y);
1103 			if(hasBkColor) {
1104 				FreeImage_SetBackgroundColor(dib, &rgbBkColor);
1105 			}
1106 			if(key_value_pair.size()) {
1107 				for(tEXtMAP::iterator j = key_value_pair.begin(); j != key_value_pair.end(); j++) {
1108 					std::string key = (*j).first;
1109 					std::string value = (*j).second;
1110 					mng_SetKeyValue(FIMD_COMMENTS, dib, key.c_str(), value.c_str());
1111 				}
1112 			}
1113 		}
1114 
1115 		return dib;
1116 
1117 	} catch(const char *text) {
1118 		FreeImage_CloseMemory(hJpegMemory);
1119 		FreeImage_CloseMemory(hPngMemory);
1120 		FreeImage_CloseMemory(hIDATMemory);
1121 		free(mChunk);
1122 		free(PLTE_file_chunk);
1123 		FreeImage_Unload(dib);
1124 		FreeImage_Unload(dib_alpha);
1125 		if(text) {
1126 			FreeImage_OutputMessageProc(format_id, text);
1127 		}
1128 		return NULL;
1129 	}
1130 }
1131 
1132 // --------------------------------------------------------------------------
1133 
1134 /**
1135 Write a FIBITMAP to a JNG stream
1136 @param format_id ID of the caller
1137 @param io Stream i/o functions
1138 @param dib Image to be saved
1139 @param handle Stream handle
1140 @param flags Saving flags
1141 @return Returns TRUE if successful, returns FALSE otherwise
1142 */
1143 BOOL
mng_WriteJNG(int format_id,FreeImageIO * io,FIBITMAP * dib,fi_handle handle,int flags)1144 mng_WriteJNG(int format_id, FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int flags) {
1145 	DWORD jng_width = 0;
1146 	DWORD jng_height = 0;
1147 	BYTE jng_color_type = 0;
1148 	BYTE jng_image_sample_depth = 8;
1149 	BYTE jng_image_compression_method = 8;	//  8: ISO-10918-1 Huffman-coded baseline JPEG.
1150 	BYTE jng_image_interlace_method = 0;
1151 
1152 	BYTE jng_alpha_sample_depth = 0;
1153 	BYTE jng_alpha_compression_method = 0;
1154 	BYTE jng_alpha_filter_method = 0;
1155 	BYTE jng_alpha_interlace_method = 0;
1156 
1157 	BYTE buffer[16];
1158 
1159 	FIMEMORY *hJngMemory = NULL;
1160 	FIMEMORY *hJpegMemory = NULL;
1161 	FIMEMORY *hPngMemory = NULL;
1162 
1163 	FIBITMAP *dib_rgb = NULL;
1164 	FIBITMAP *dib_alpha = NULL;
1165 
1166 	if(!dib || (FreeImage_GetImageType(dib) != FIT_BITMAP)) {
1167 		return FALSE;
1168 	}
1169 
1170 	unsigned bpp = FreeImage_GetBPP(dib);
1171 
1172 	switch(bpp) {
1173 		case 8:
1174 			if(FreeImage_GetColorType(dib) == FIC_MINISBLACK) {
1175 				dib_rgb = dib;
1176 				jng_color_type = MNG_COLORTYPE_JPEGGRAY;
1177 			} else {
1178 				// JPEG plugin will convert other types (FIC_MINISWHITE, FIC_PALETTE) to 24-bit on the fly
1179 				//dib_rgb = FreeImage_ConvertTo24Bits(dib);
1180 				dib_rgb = dib;
1181 				jng_color_type = MNG_COLORTYPE_JPEGCOLOR;
1182 
1183 			}
1184 			break;
1185 		case 24:
1186 			dib_rgb = dib;
1187 			jng_color_type = MNG_COLORTYPE_JPEGCOLOR;
1188 			break;
1189 		case 32:
1190 			dib_rgb = FreeImage_ConvertTo24Bits(dib);
1191 			jng_color_type = MNG_COLORTYPE_JPEGCOLORA;
1192 			jng_alpha_sample_depth = 8;
1193 			break;
1194 		default:
1195 			return FALSE;
1196 	}
1197 
1198 	jng_width = (DWORD)FreeImage_GetWidth(dib);
1199 	jng_height = (DWORD)FreeImage_GetHeight(dib);
1200 
1201 	try {
1202 		hJngMemory = FreeImage_OpenMemory();
1203 
1204 		// --- write JNG file signature ---
1205 		FreeImage_WriteMemory(g_jng_signature, 1, 8, hJngMemory);
1206 
1207 		// --- write a JHDR chunk ---
1208 		SwapLong(&jng_width);
1209 		SwapLong(&jng_height);
1210 		memcpy(&buffer[0], &jng_width, 4);
1211 		memcpy(&buffer[4], &jng_height, 4);
1212 		SwapLong(&jng_width);
1213 		SwapLong(&jng_height);
1214 		buffer[8] = jng_color_type;
1215 		buffer[9] = jng_image_sample_depth;
1216 		buffer[10] = jng_image_compression_method;
1217 		buffer[11] = jng_image_interlace_method;
1218 		buffer[12] = jng_alpha_sample_depth;
1219 		buffer[13] = jng_alpha_compression_method;
1220 		buffer[14] = jng_alpha_filter_method;
1221 		buffer[15] = jng_alpha_interlace_method;
1222 		mng_WriteChunk(mng_JHDR, &buffer[0], 16, hJngMemory);
1223 
1224 		// --- write a sequence of JDAT chunks ---
1225 		hJpegMemory = FreeImage_OpenMemory();
1226 		flags |= JPEG_BASELINE;
1227 		if(!FreeImage_SaveToMemory(FIF_JPEG, dib_rgb, hJpegMemory, flags)) {
1228 			throw (const char*)NULL;
1229 		}
1230 		if(dib_rgb != dib) {
1231 			FreeImage_Unload(dib_rgb);
1232 			dib_rgb = NULL;
1233 		}
1234 		{
1235 			BYTE *jpeg_data = NULL;
1236 			DWORD size_in_bytes = 0;
1237 
1238 			// get a pointer to the stream buffer
1239 			FreeImage_AcquireMemory(hJpegMemory, &jpeg_data, &size_in_bytes);
1240 			// write chunks
1241 			for(DWORD k = 0; k < size_in_bytes;) {
1242 				DWORD bytes_left = size_in_bytes - k;
1243 				DWORD chunk_size = MIN(JPEG_CHUNK_SIZE, bytes_left);
1244 				mng_WriteChunk(mng_JDAT, &jpeg_data[k], chunk_size, hJngMemory);
1245 				k += chunk_size;
1246 			}
1247 		}
1248 		FreeImage_CloseMemory(hJpegMemory);
1249 		hJpegMemory = NULL;
1250 
1251 		// --- write alpha layer as a sequence of IDAT chunk ---
1252 		if((bpp == 32) && (jng_color_type == MNG_COLORTYPE_JPEGCOLORA)) {
1253 			dib_alpha = FreeImage_GetChannel(dib, FICC_ALPHA);
1254 
1255 			hPngMemory = FreeImage_OpenMemory();
1256 			if(!FreeImage_SaveToMemory(FIF_PNG, dib_alpha, hPngMemory, PNG_DEFAULT)) {
1257 				throw (const char*)NULL;
1258 			}
1259 			FreeImage_Unload(dib_alpha);
1260 			dib_alpha = NULL;
1261 			// get the IDAT chunk
1262 			{
1263 				BOOL bResult = FALSE;
1264 				DWORD start_pos = 0;
1265 				DWORD next_pos = 0;
1266 				long offset = 8;
1267 
1268 				do {
1269 					// find the next IDAT chunk from 'offset' position
1270 					bResult = mng_FindChunk(hPngMemory, mng_IDAT, offset, &start_pos, &next_pos);
1271 					if(!bResult) break;
1272 
1273 					BYTE *png_data = NULL;
1274 					DWORD size_in_bytes = 0;
1275 
1276 					// get a pointer to the stream buffer
1277 					FreeImage_AcquireMemory(hPngMemory, &png_data, &size_in_bytes);
1278 					// write the IDAT chunk
1279 					mng_WriteChunk(mng_IDAT, &png_data[start_pos+8], next_pos - start_pos - 12, hJngMemory);
1280 
1281 					offset = next_pos;
1282 
1283 				} while(bResult);
1284 			}
1285 
1286 			FreeImage_CloseMemory(hPngMemory);
1287 			hPngMemory = NULL;
1288 		}
1289 
1290 		// --- write a IEND chunk ---
1291 		mng_WriteChunk(mng_IEND, NULL, 0, hJngMemory);
1292 
1293 		// write the JNG on output stream
1294 		{
1295 			BYTE *jng_data = NULL;
1296 			DWORD size_in_bytes = 0;
1297 			FreeImage_AcquireMemory(hJngMemory, &jng_data, &size_in_bytes);
1298 			io->write_proc(jng_data, 1, size_in_bytes, handle);
1299 		}
1300 
1301 		FreeImage_CloseMemory(hJngMemory);
1302 		FreeImage_CloseMemory(hJpegMemory);
1303 		FreeImage_CloseMemory(hPngMemory);
1304 
1305 		return TRUE;
1306 
1307 	} catch(const char *text) {
1308 		FreeImage_CloseMemory(hJngMemory);
1309 		FreeImage_CloseMemory(hJpegMemory);
1310 		FreeImage_CloseMemory(hPngMemory);
1311 		if(dib_rgb && (dib_rgb != dib)) {
1312 			FreeImage_Unload(dib_rgb);
1313 		}
1314 		FreeImage_Unload(dib_alpha);
1315 		if(text) {
1316 			FreeImage_OutputMessageProc(format_id, text);
1317 		}
1318 		return FALSE;
1319 	}
1320 }
1321