Coverage Report

Created: 2025-06-22 06:41

/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/MNGHelper.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#define MNG_COLORTYPE_JPEGGRAY           8       /* JHDR */
38
0
#define MNG_COLORTYPE_JPEGCOLOR         10
39
#define MNG_COLORTYPE_JPEGGRAYA         12
40
0
#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 
191
0
mng_GetChunckType(const BYTE *mChunkName) {
192
0
  if(memcmp(mChunkName, mng_MHDR, 4) == 0) {
193
0
    return MHDR;
194
0
  }
195
0
  if(memcmp(mChunkName, mng_LOOP, 4) == 0) {
196
0
    return LOOP;
197
0
  }
198
0
  if(memcmp(mChunkName, mng_DEFI, 4) == 0) {
199
0
    return DEFI;
200
0
  }
201
0
  if(memcmp(mChunkName, mng_PLTE, 4) == 0) {
202
0
    return PLTE;
203
0
  }
204
0
  if(memcmp(mChunkName, mng_tRNS, 4) == 0) {
205
0
    return tRNS;
206
0
  }
207
0
  if(memcmp(mChunkName, mng_IHDR, 4) == 0) {
208
0
    return IHDR;
209
0
  }
210
0
  if(memcmp(mChunkName, mng_JHDR, 4) == 0) {
211
0
    return JHDR;
212
0
  }
213
0
  if(memcmp(mChunkName, mng_MEND, 4) == 0) {
214
0
    return MEND;
215
0
  }
216
0
  if(memcmp(mChunkName, mng_IEND, 4) == 0) {
217
0
    return IEND;
218
0
  }
219
0
  if(memcmp(mChunkName, mng_JDAT, 4) == 0) {
220
0
    return JDAT;
221
0
  }
222
0
  if(memcmp(mChunkName, mng_IDAT, 4) == 0) {
223
0
    return IDAT;
224
0
  }
225
0
  if(memcmp(mChunkName, mng_JDAA, 4) == 0) {
226
0
    return JDAA;
227
0
  }
228
0
  if(memcmp(mChunkName, mng_gAMA, 4) == 0) {
229
0
    return gAMA;
230
0
  }
231
0
  if(memcmp(mChunkName, mng_pHYs, 4) == 0) {
232
0
    return pHYs;
233
0
  }
234
0
  if(memcmp(mChunkName, mng_bKGD, 4) == 0) {
235
0
    return bKGD;
236
0
  }
237
0
  if(memcmp(mChunkName, mng_tEXt, 4) == 0) {
238
0
    return tEXt;
239
0
  }
240
241
0
  return UNKNOWN_CHUNCK;
242
0
}
243
244
inline void
245
0
mng_SwapShort(WORD *sp) {
246
0
#ifndef FREEIMAGE_BIGENDIAN
247
0
  SwapShort(sp);
248
0
#endif
249
0
}
250
251
inline void
252
0
mng_SwapLong(DWORD *lp) {
253
0
#ifndef FREEIMAGE_BIGENDIAN
254
0
  SwapLong(lp);
255
0
#endif
256
0
}
257
258
/**
259
Returns the size, in bytes, of a FreeImageIO stream, from the current position. 
260
*/
261
static long
262
0
mng_LOF(FreeImageIO *io, fi_handle handle) {
263
0
  long start_pos = io->tell_proc(handle);
264
0
  io->seek_proc(handle, 0, SEEK_END);
265
0
  long file_length = io->tell_proc(handle);
266
0
  io->seek_proc(handle, start_pos, SEEK_SET);
267
0
  return file_length;
268
0
}
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 
281
0
mng_CountPNGChunks(FreeImageIO *io, fi_handle handle, long inPos, unsigned *m_TotalBytesOfChunks) {
282
0
  long mLOF;
283
0
  long mPos;
284
0
  BOOL mEnd = FALSE;
285
0
  DWORD mLength = 0;
286
0
  BYTE mChunkName[5];
287
288
0
  *m_TotalBytesOfChunks = 0;
289
290
  // get the length of the file
291
0
  mLOF = mng_LOF(io, handle);
292
293
  // go to the start of the file
294
0
  io->seek_proc(handle, inPos, SEEK_SET);
295
296
0
  try {
297
    // parse chunks
298
0
    while(mEnd == FALSE) {
299
      // chunk length
300
0
      mPos = io->tell_proc(handle);
301
0
      if(mPos + 4 > mLOF) {
302
0
        throw(1);
303
0
      }
304
0
      io->read_proc(&mLength, 1, 4, handle);
305
0
      mng_SwapLong(&mLength);
306
      // chunk name
307
0
      mPos = io->tell_proc(handle);
308
0
      if(mPos + 4 > mLOF) {
309
0
        throw(1);
310
0
      }
311
0
      io->read_proc(&mChunkName[0], 1, 4, handle);
312
0
      mChunkName[4] = '\0';
313
314
      // go to next chunk
315
0
      mPos = io->tell_proc(handle);
316
      // 4 = size of the CRC
317
0
      if(mPos + (long)mLength + 4 > mLOF) {
318
0
        throw(1);
319
0
      }
320
0
      io->seek_proc(handle, mLength + 4, SEEK_CUR);
321
322
0
      switch( mng_GetChunckType(mChunkName) ) {
323
0
        case IHDR:
324
0
          if(mLength != 13) {
325
0
            throw(1);
326
0
          }
327
0
          break;
328
        
329
0
        case IEND:
330
0
          mEnd = TRUE;   
331
          // the length below includes 4 bytes CRC, but no bytes for Length
332
0
          *m_TotalBytesOfChunks = io->tell_proc(handle) - inPos;
333
0
          break;    
334
        
335
0
        case UNKNOWN_CHUNCK:
336
0
        default:
337
0
          break;
338
0
      }
339
340
0
    } // while(!mEnd)
341
342
0
    return TRUE;
343
    
344
0
  } catch(int) {
345
0
    return FALSE;
346
0
  }
347
0
}
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 
359
0
mng_FindChunk(FIMEMORY *hPngMemory, BYTE *chunk_name, long offset, DWORD *start_pos, DWORD *next_pos) {
360
0
  DWORD mLength = 0;
361
362
0
  BYTE *data = NULL;
363
0
  DWORD size_in_bytes = 0;
364
365
0
  *start_pos = 0;
366
0
  *next_pos = 0;
367
368
  // get a pointer to the stream buffer
369
0
  FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
370
0
  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
0
    return FALSE;
373
0
  }
374
375
0
  try {
376
  
377
    // skip the signature and/or any following chunk(s)
378
0
    DWORD chunk_pos = offset;
379
380
0
    while(1) {
381
      // get chunk length
382
0
      if(chunk_pos + 4 > size_in_bytes) {
383
0
        break;
384
0
      }
385
386
0
      memcpy(&mLength, &data[chunk_pos], 4);
387
0
      mng_SwapLong(&mLength);
388
0
      chunk_pos += 4;
389
390
0
      const DWORD next_chunk_pos = chunk_pos + 4 + mLength + 4;
391
0
      if(next_chunk_pos > size_in_bytes) {
392
0
        break;
393
0
      }
394
395
      // get chunk name
396
0
      if(memcmp(&data[chunk_pos], chunk_name, 4) == 0) {
397
0
        chunk_pos -= 4; // found chunk
398
0
        *start_pos = chunk_pos;
399
0
        *next_pos = next_chunk_pos;
400
0
        return TRUE;
401
0
      }
402
      
403
0
      chunk_pos = next_chunk_pos;
404
0
    }
405
406
0
    return FALSE;
407
408
0
  } catch(int) {
409
0
    return FALSE;
410
0
  }
411
0
}
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 
421
0
mng_CopyRemoveChunks(FIMEMORY *hPngMemory, DWORD start_pos, DWORD next_pos) {
422
0
  BYTE *data = NULL;
423
0
  DWORD size_in_bytes = 0;
424
425
  // length of the chunk to remove
426
0
  DWORD chunk_length = next_pos - start_pos;
427
0
  if(chunk_length == 0) {
428
0
    return TRUE;
429
0
  }
430
431
  // get a pointer to the stream buffer
432
0
  FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
433
0
  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
0
    return FALSE;
436
0
  }
437
  
438
  // new file length
439
0
  unsigned buffer_size = size_in_bytes + chunk_length;
440
441
0
  BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE));
442
0
  if(!buffer) {
443
0
    return FALSE;
444
0
  }
445
0
  memcpy(&buffer[0], &data[0], start_pos);
446
0
  memcpy(&buffer[start_pos], &data[next_pos], size_in_bytes - next_pos);
447
448
  // seek to the start of the stream
449
0
  FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
450
  // re-write the stream
451
0
  FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory);
452
453
0
  free(buffer);
454
455
0
  return TRUE;
456
0
}
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 
466
0
mng_CopyInsertChunks(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, DWORD inChunkLength, DWORD start_pos, DWORD next_pos) {
467
0
  BYTE *data = NULL;
468
0
  DWORD size_in_bytes = 0;
469
470
  // length of the chunk to check
471
0
  DWORD chunk_length = next_pos - start_pos;
472
0
  if(chunk_length == 0) {
473
0
    return TRUE;
474
0
  }
475
476
  // get a pointer to the stream buffer
477
0
  FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes);
478
0
  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
0
    return FALSE;
481
0
  }
482
  
483
  // new file length
484
0
  unsigned buffer_size = inChunkLength + size_in_bytes;
485
486
0
  BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE));
487
0
  if(!buffer) {
488
0
    return FALSE;
489
0
  }
490
0
  unsigned p = 0;
491
0
  memcpy(&buffer[p], &data[0], start_pos);
492
0
  p += start_pos;
493
0
  memcpy(&buffer[p], inInsertChunk, inChunkLength);
494
0
  p += inChunkLength;
495
0
  memcpy(&buffer[p], &data[start_pos], size_in_bytes - start_pos);
496
497
  // seek to the start of the stream
498
0
  FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
499
  // re-write the stream
500
0
  FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory);
501
502
0
  free(buffer);
503
504
0
  return TRUE;
505
0
}
506
507
static BOOL 
508
0
mng_RemoveChunk(FIMEMORY *hPngMemory, BYTE *chunk_name) {
509
0
  BOOL bResult = FALSE;
510
511
0
  DWORD start_pos = 0;
512
0
  DWORD next_pos = 0;
513
  
514
0
  bResult = mng_FindChunk(hPngMemory, chunk_name, 8, &start_pos, &next_pos);
515
0
  if(!bResult) {
516
0
    return FALSE;
517
0
  }
518
519
0
  bResult = mng_CopyRemoveChunks(hPngMemory, start_pos, next_pos);
520
0
  if(!bResult) {
521
0
    return FALSE;
522
0
  }
523
524
0
  return TRUE;
525
0
}
526
527
static BOOL 
528
0
mng_InsertChunk(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, unsigned chunk_length) {
529
0
  BOOL bResult = FALSE;
530
531
0
  DWORD start_pos = 0;
532
0
  DWORD next_pos = 0;
533
  
534
0
  bResult = mng_FindChunk(hPngMemory, inNextChunkName, 8, &start_pos, &next_pos);
535
0
  if(!bResult) {
536
0
    return FALSE;
537
0
  }
538
539
0
  bResult = mng_CopyInsertChunks(hPngMemory, inNextChunkName, inInsertChunk, chunk_length, start_pos, next_pos);
540
0
  if(!bResult) {
541
0
    return FALSE;
542
0
  }
543
544
0
  return TRUE;
545
0
}
546
547
static FIBITMAP* 
548
0
mng_LoadFromMemoryHandle(FIMEMORY *hmem, int flags = 0) {
549
0
  long offset = 0;
550
0
  FIBITMAP *dib = NULL;
551
552
0
  if(hmem) {
553
    // seek to the start of the stream
554
0
    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
0
    FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
559
0
    if(fif != FIF_UNKNOWN) {
560
0
      dib = FreeImage_LoadFromMemory(fif, hmem, flags);
561
0
    }
562
0
  }
563
  
564
0
  return dib;
565
0
}
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
575
0
mng_WriteChunk(BYTE *chunk_name, BYTE *chunk_data, DWORD length, FIMEMORY *hPngMemory) {
576
0
  DWORD crc_file = 0;
577
  // write a PNG chunk ...
578
  // - length
579
0
  mng_SwapLong(&length);
580
0
  FreeImage_WriteMemory(&length, 1, 4, hPngMemory);
581
0
  mng_SwapLong(&length);
582
  // - chunk name
583
0
  FreeImage_WriteMemory(chunk_name, 1, 4, hPngMemory);
584
0
  if(chunk_data && length) {
585
    // - chunk data
586
0
    FreeImage_WriteMemory(chunk_data, 1, length, hPngMemory);
587
    // - crc
588
0
    crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4);
589
0
    crc_file = FreeImage_ZLibCRC32(crc_file, chunk_data, length);
590
0
    mng_SwapLong(&crc_file);
591
0
    FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory);
592
0
  } else {
593
    // - crc
594
0
    crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4);
595
0
    mng_SwapLong(&crc_file);
596
0
    FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory);
597
0
  }
598
599
0
}
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 
614
0
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
0
  BYTE data[14];
618
619
  // wrap the IDAT chunk as a PNG stream
620
621
  // write PNG file signature
622
0
  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
0
  mng_SwapLong(&jng_width);
637
0
  mng_SwapLong(&jng_height);
638
0
  memcpy(&data[0], &jng_width, 4);
639
0
  memcpy(&data[4], &jng_height, 4);
640
0
  mng_SwapLong(&jng_width);
641
0
  mng_SwapLong(&jng_height);
642
0
  data[8] = jng_alpha_sample_depth;
643
0
  data[9] = 0;  // color_type gray (jng_color_type)
644
0
  data[10] = 0; // compression method 0 (jng_alpha_compression_method)
645
0
  data[11] = 0; // filter_method 0 (jng_alpha_filter_method)
646
0
  data[12] = 0; // interlace_method 0 (jng_alpha_interlace_method)
647
648
0
  mng_WriteChunk(mng_IHDR, &data[0], 13, hPngMemory);
649
650
  // write a IDAT chunk ...
651
0
  mng_WriteChunk(mng_IDAT, mChunk, mLength, hPngMemory);
652
653
  // write a IEND chunk ...
654
0
  mng_WriteChunk(mng_IEND, NULL, 0, hPngMemory);
655
656
0
}
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 
670
0
mng_SetKeyValue(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, const char *value) {
671
0
  if(!dib || !key || !value) {
672
0
    return FALSE;
673
0
  }
674
  // create a tag
675
0
  FITAG *tag = FreeImage_CreateTag();
676
0
  if(tag) {
677
0
    BOOL bSuccess = TRUE;
678
    // fill the tag
679
0
    DWORD tag_length = (DWORD)(strlen(value) + 1);
680
0
    bSuccess &= FreeImage_SetTagKey(tag, key);
681
0
    bSuccess &= FreeImage_SetTagLength(tag, tag_length);
682
0
    bSuccess &= FreeImage_SetTagCount(tag, tag_length);
683
0
    bSuccess &= FreeImage_SetTagType(tag, FIDT_ASCII);
684
0
    bSuccess &= FreeImage_SetTagValue(tag, value);
685
0
    if(bSuccess) {
686
      // set the tag
687
0
      FreeImage_SetMetadata(model, dib, FreeImage_GetTagKey(tag), tag);
688
0
    }
689
0
    FreeImage_DeleteTag(tag);
690
0
    return bSuccess;
691
0
  }
692
693
0
  return FALSE;
694
0
}
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 
704
0
mng_SetMetadata_tEXt(tEXtMAP &key_value_pair, const BYTE *mChunk, DWORD mLength) {
705
0
  std::string key;
706
0
  std::string value;
707
0
  BYTE *buffer = (BYTE*)malloc(mLength * sizeof(BYTE));
708
0
  if(!buffer) {
709
0
    return FALSE;
710
0
  }
711
0
  DWORD pos = 0;
712
713
0
  memset(buffer, 0, mLength * sizeof(BYTE));
714
715
0
  for(DWORD i = 0; i < mLength; i++) {
716
0
    buffer[pos++] = mChunk[i];
717
0
    if(mChunk[i] == '\0') {
718
0
      if(key.size() == 0) {
719
0
        key = (char*)buffer;
720
0
        pos = 0;
721
0
        memset(buffer, 0, mLength * sizeof(BYTE));
722
0
      } else {
723
0
        break;
724
0
      }
725
0
    }
726
0
  }
727
0
  value = (char*)buffer;
728
0
  free(buffer);
729
730
0
  key_value_pair[key] = value;
731
732
0
  return TRUE;
733
0
}
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* 
747
0
mng_ReadChunks(int format_id, FreeImageIO *io, fi_handle handle, long Offset, int flags = 0) {
748
0
  DWORD mLength = 0;
749
0
  BYTE mChunkName[5];
750
0
  BYTE *mChunk = NULL;
751
0
  DWORD crc_file;
752
0
  long LastOffset;
753
0
  long mOrigPos;
754
0
  BYTE *PLTE_file_chunk = NULL; // whole PLTE chunk (lentgh, name, array, crc)
755
0
  DWORD PLTE_file_size = 0;   // size of PLTE chunk
756
757
0
  BOOL m_HasGlobalPalette = FALSE; // may turn to TRUE in PLTE chunk
758
0
  unsigned m_TotalBytesOfChunks = 0;
759
0
  FIBITMAP *dib = NULL;
760
0
  FIBITMAP *dib_alpha = NULL;
761
762
0
  FIMEMORY *hJpegMemory = NULL;
763
0
  FIMEMORY *hPngMemory = NULL;
764
0
  FIMEMORY *hIDATMemory = NULL;
765
766
  // ---
767
0
  DWORD jng_width = 0;
768
0
  DWORD jng_height = 0;
769
0
  BYTE jng_color_type = 0;
770
0
  BYTE jng_image_sample_depth = 0;
771
0
  BYTE jng_image_compression_method = 0;
772
773
0
  BYTE jng_alpha_sample_depth = 0;
774
0
  BYTE jng_alpha_compression_method = 0;
775
0
  BYTE jng_alpha_filter_method = 0;
776
0
  BYTE jng_alpha_interlace_method = 0;
777
778
0
  DWORD mng_frame_width = 0;
779
0
  DWORD mng_frame_height = 0;
780
0
  DWORD mng_ticks_per_second = 0;
781
0
  DWORD mng_nominal_layer_count = 0;
782
0
  DWORD mng_nominal_frame_count = 0;
783
0
  DWORD mng_nominal_play_time = 0;
784
0
  DWORD mng_simplicity_profile = 0;
785
786
787
0
  DWORD res_x = 2835; // 72 dpi
788
0
  DWORD res_y = 2835; // 72 dpi
789
0
  RGBQUAD rgbBkColor = {0, 0, 0, 0};
790
0
  WORD bk_red, bk_green, bk_blue;
791
0
  BOOL hasBkColor = FALSE;
792
0
  BOOL mHasIDAT = FALSE;
793
794
0
  tEXtMAP key_value_pair;
795
796
  // ---
797
798
0
  BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
799
  
800
  // get the file size
801
0
  const long mLOF = mng_LOF(io, handle);
802
  // go to the first chunk
803
0
  io->seek_proc(handle, Offset, SEEK_SET);
804
805
0
  try {
806
0
    BOOL mEnd = FALSE;
807
808
0
    while(mEnd == FALSE) {
809
      // start of the chunk
810
0
      LastOffset = io->tell_proc(handle);
811
      // read length
812
0
      mLength = 0;      
813
0
      io->read_proc(&mLength, 1, sizeof(mLength), handle);
814
0
      mng_SwapLong(&mLength);
815
      // read name      
816
0
      io->read_proc(&mChunkName[0], 1, 4, handle);
817
0
      mChunkName[4] = '\0';
818
819
0
      if(mLength > 0) {
820
0
        mChunk = (BYTE*)realloc(mChunk, mLength);
821
0
        if(!mChunk) {
822
0
          FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
823
0
          throw (const char*)NULL;
824
0
        }        
825
0
        Offset = io->tell_proc(handle);
826
0
        if(Offset + (long)mLength > mLOF) {
827
0
          FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of file", mChunkName);
828
0
          throw (const char*)NULL;
829
0
        }
830
        // read chunk
831
0
        io->read_proc(mChunk, 1, mLength, handle);
832
0
      }
833
      // read crc
834
0
      io->read_proc(&crc_file, 1, sizeof(crc_file), handle);
835
0
      mng_SwapLong(&crc_file);
836
      // check crc
837
0
      DWORD crc_check = FreeImage_ZLibCRC32(0, &mChunkName[0], 4);
838
0
      crc_check = FreeImage_ZLibCRC32(crc_check, mChunk, mLength);
839
0
      if(crc_check != crc_file) {
840
0
        FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: bad CRC", mChunkName);
841
0
        throw (const char*)NULL;
842
0
      }    
843
844
0
      switch( mng_GetChunckType(mChunkName) ) {
845
0
        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
0
          if(mLength == 28) {
849
0
            memcpy(&mng_frame_width, &mChunk[0], 4);
850
0
            memcpy(&mng_frame_height, &mChunk[4], 4);
851
0
            memcpy(&mng_ticks_per_second, &mChunk[8], 4);
852
0
            memcpy(&mng_nominal_layer_count, &mChunk[12], 4);
853
0
            memcpy(&mng_nominal_frame_count, &mChunk[16], 4);
854
0
            memcpy(&mng_nominal_play_time, &mChunk[20], 4);
855
0
            memcpy(&mng_simplicity_profile, &mChunk[24], 4);
856
857
0
            mng_SwapLong(&mng_frame_width);
858
0
            mng_SwapLong(&mng_frame_height);
859
0
            mng_SwapLong(&mng_ticks_per_second);
860
0
            mng_SwapLong(&mng_nominal_layer_count);
861
0
            mng_SwapLong(&mng_nominal_frame_count);
862
0
            mng_SwapLong(&mng_nominal_play_time);
863
0
            mng_SwapLong(&mng_simplicity_profile);
864
865
0
          } else {
866
0
            FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: size is %d instead of 28", mChunkName, mLength);
867
0
          }
868
0
          break;
869
870
0
        case MEND:
871
0
          mEnd = TRUE;
872
0
          break;
873
874
0
        case LOOP:
875
0
        case ENDL:
876
0
          break;
877
0
        case DEFI:
878
0
          break;
879
0
        case SAVE:
880
0
        case SEEK:
881
0
        case TERM:
882
0
          break;
883
0
        case BACK:
884
0
          break;
885
886
          // Global "PLTE" and "tRNS" (if any).  PNG "PLTE" will be of 0 byte, as it uses global data.
887
0
        case PLTE:  // Global
888
0
          m_HasGlobalPalette = TRUE;
889
0
          PLTE_file_size = mLength + 12; // (lentgh, name, array, crc) = (4, 4, mLength, 4)
890
0
          PLTE_file_chunk = (BYTE*)realloc(PLTE_file_chunk, PLTE_file_size);
891
0
          if(!PLTE_file_chunk) {
892
0
            FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
893
0
            throw (const char*)NULL;
894
0
          } else {
895
0
            mOrigPos = io->tell_proc(handle);
896
            // seek to the start of the chunk
897
0
            io->seek_proc(handle, LastOffset, SEEK_SET);
898
            // load the whole chunk
899
0
            io->read_proc(PLTE_file_chunk, 1, PLTE_file_size, handle);
900
            // go to the start of the next chunk
901
0
            io->seek_proc(handle, mOrigPos, SEEK_SET);
902
0
          }
903
0
          break;
904
905
0
        case tRNS:  // Global
906
0
          break;
907
          
908
0
        case IHDR:
909
0
          Offset = LastOffset;
910
          // parse the PNG file and get its file size
911
0
          if(mng_CountPNGChunks(io, handle, Offset, &m_TotalBytesOfChunks) == FALSE) {
912
            // reach an unexpected end of file
913
0
            mEnd = TRUE;
914
0
            FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of PNG file", mChunkName);
915
0
            break;
916
0
          }
917
          
918
          // wrap the { IHDR, ..., IEND } chunks as a PNG stream
919
0
          if(hPngMemory == NULL) {
920
0
            hPngMemory = FreeImage_OpenMemory();
921
0
          }
922
923
0
          mOrigPos = io->tell_proc(handle);
924
925
          // write PNG file signature
926
0
          FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET);
927
0
          FreeImage_WriteMemory(g_png_signature, 1, 8, hPngMemory);
928
929
0
          mChunk = (BYTE*)realloc(mChunk, m_TotalBytesOfChunks);
930
0
          if(!mChunk) {
931
0
            FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName);
932
0
            throw (const char*)NULL;
933
0
          }
934
          
935
          // on calling CountPNGChunks earlier, we were in Offset pos,
936
          // go back there
937
0
          io->seek_proc(handle, Offset, SEEK_SET);
938
0
          io->read_proc(mChunk, 1, m_TotalBytesOfChunks, handle);
939
          // Put back to original pos
940
0
          io->seek_proc(handle, mOrigPos, SEEK_SET);
941
          // write the PNG chunks
942
0
          FreeImage_WriteMemory(mChunk, 1, m_TotalBytesOfChunks, hPngMemory);
943
944
          // plug in global PLTE if local PLTE exists
945
0
          if(m_HasGlobalPalette) {
946
            // ensure we remove some local chunks, so that global
947
            // "PLTE" can be inserted right before "IDAT".
948
0
            mng_RemoveChunk(hPngMemory, mng_PLTE);
949
0
            mng_RemoveChunk(hPngMemory, mng_tRNS);
950
0
            mng_RemoveChunk(hPngMemory, mng_bKGD);
951
            // insert global "PLTE" chunk in its entirety before "IDAT"
952
0
            mng_InsertChunk(hPngMemory, mng_IDAT, PLTE_file_chunk, PLTE_file_size);
953
0
          }
954
955
0
          if(dib) FreeImage_Unload(dib);
956
0
          dib = mng_LoadFromMemoryHandle(hPngMemory, flags);
957
958
          // stop after the first image
959
0
          mEnd = TRUE;
960
0
          break;
961
962
0
        case JHDR:
963
0
          if(mLength == 16) {
964
0
            memcpy(&jng_width, &mChunk[0], 4);
965
0
            memcpy(&jng_height, &mChunk[4], 4);
966
0
            mng_SwapLong(&jng_width);
967
0
            mng_SwapLong(&jng_height);
968
969
0
            jng_color_type = mChunk[8];
970
0
            jng_image_sample_depth = mChunk[9];
971
0
            jng_image_compression_method = mChunk[10];
972
            //BYTE jng_image_interlace_method = mChunk[11]; // for debug only
973
974
0
            jng_alpha_sample_depth = mChunk[12];
975
0
            jng_alpha_compression_method = mChunk[13];
976
0
            jng_alpha_filter_method = mChunk[14];
977
0
            jng_alpha_interlace_method = mChunk[15];
978
0
          } else {
979
0
            FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: invalid chunk length", mChunkName);
980
0
            throw (const char*)NULL;
981
0
          }
982
0
          break;
983
984
0
        case JDAT:
985
0
          if(hJpegMemory == NULL) {
986
0
            hJpegMemory = FreeImage_OpenMemory();
987
0
          }
988
          // as there may be several JDAT chunks, concatenate them
989
0
          FreeImage_WriteMemory(mChunk, 1, mLength, hJpegMemory);
990
0
          break;
991
992
0
        case IDAT:
993
0
          if(!header_only && (jng_alpha_compression_method == 0)) {
994
            // PNG grayscale IDAT format
995
0
            if(hIDATMemory == NULL) {
996
0
              hIDATMemory = FreeImage_OpenMemory();
997
0
              mHasIDAT = TRUE;
998
0
            }
999
            // as there may be several IDAT chunks, concatenate them
1000
0
            FreeImage_WriteMemory(mChunk, 1, mLength, hIDATMemory);
1001
0
          }
1002
0
          break;
1003
1004
0
        case IEND:
1005
0
          if(!hJpegMemory) {
1006
0
            mEnd = TRUE;
1007
0
            break;
1008
0
          }
1009
          // load the JPEG
1010
0
          if(dib) {
1011
0
            FreeImage_Unload(dib);
1012
0
          }
1013
0
          dib = mng_LoadFromMemoryHandle(hJpegMemory, flags);
1014
1015
          // load the PNG alpha layer
1016
0
          if(mHasIDAT) {
1017
0
            BYTE *data = NULL;
1018
0
            DWORD size_in_bytes = 0;
1019
1020
            // get a pointer to the IDAT buffer
1021
0
            FreeImage_AcquireMemory(hIDATMemory, &data, &size_in_bytes);
1022
0
            if(data && size_in_bytes) {
1023
              // wrap the IDAT chunk as a PNG stream
1024
0
              if(hPngMemory == NULL) {
1025
0
                hPngMemory = FreeImage_OpenMemory();
1026
0
              }
1027
0
              mng_WritePNGStream(jng_width, jng_height, jng_alpha_sample_depth, data, size_in_bytes, hPngMemory);
1028
              // load the PNG
1029
0
              if(dib_alpha) {
1030
0
                FreeImage_Unload(dib_alpha);
1031
0
              }
1032
0
              dib_alpha = mng_LoadFromMemoryHandle(hPngMemory, flags);
1033
0
            }
1034
0
          }
1035
          // stop the parsing
1036
0
          mEnd = TRUE;
1037
0
          break;
1038
1039
0
        case JDAA:
1040
0
          break;
1041
1042
0
        case gAMA:
1043
0
          break;
1044
1045
0
        case pHYs:
1046
          // unit is pixels per meter
1047
0
          memcpy(&res_x, &mChunk[0], 4);
1048
0
          mng_SwapLong(&res_x);
1049
0
          memcpy(&res_y, &mChunk[4], 4);
1050
0
          mng_SwapLong(&res_y);
1051
0
          break;
1052
1053
0
        case bKGD:
1054
0
          memcpy(&bk_red, &mChunk[0], 2);
1055
0
          mng_SwapShort(&bk_red);
1056
0
          rgbBkColor.rgbRed = (BYTE)bk_red;
1057
0
          memcpy(&bk_green, &mChunk[2], 2);
1058
0
          mng_SwapShort(&bk_green);
1059
0
          rgbBkColor.rgbGreen = (BYTE)bk_green;
1060
0
          memcpy(&bk_blue, &mChunk[4], 2);
1061
0
          mng_SwapShort(&bk_blue);
1062
0
          rgbBkColor.rgbBlue = (BYTE)bk_blue;
1063
0
          hasBkColor = TRUE;
1064
0
          break;
1065
        
1066
0
        case tEXt:
1067
0
          mng_SetMetadata_tEXt(key_value_pair, mChunk, mLength);
1068
0
          break;
1069
1070
0
        case UNKNOWN_CHUNCK:
1071
0
        default:
1072
0
          break;
1073
1074
1075
0
      } // switch( GetChunckType )
1076
0
    } // while(!mEnd)
1077
1078
0
    FreeImage_CloseMemory(hJpegMemory);
1079
0
    FreeImage_CloseMemory(hPngMemory);
1080
0
    FreeImage_CloseMemory(hIDATMemory);
1081
0
    free(mChunk);
1082
0
    free(PLTE_file_chunk);
1083
1084
    // convert to 32-bit if a transparent layer is available
1085
0
    if(!header_only && dib_alpha) {
1086
0
      FIBITMAP *dst = FreeImage_ConvertTo32Bits(dib);
1087
0
      if((FreeImage_GetBPP(dib_alpha) == 8) && (FreeImage_GetImageType(dib_alpha) == FIT_BITMAP)) {
1088
0
        FreeImage_SetChannel(dst, dib_alpha, FICC_ALPHA);
1089
0
      } else {
1090
0
        FIBITMAP *dst_alpha = FreeImage_ConvertTo8Bits(dib_alpha);
1091
0
        FreeImage_SetChannel(dst, dst_alpha, FICC_ALPHA);
1092
0
        FreeImage_Unload(dst_alpha);
1093
0
      }      
1094
0
      FreeImage_Unload(dib);
1095
0
      dib = dst;
1096
0
    }
1097
0
    FreeImage_Unload(dib_alpha);
1098
1099
0
    if(dib) {
1100
      // set metadata
1101
0
      FreeImage_SetDotsPerMeterX(dib, res_x);
1102
0
      FreeImage_SetDotsPerMeterY(dib, res_y);
1103
0
      if(hasBkColor) {
1104
0
        FreeImage_SetBackgroundColor(dib, &rgbBkColor);
1105
0
      }
1106
0
      if(key_value_pair.size()) {
1107
0
        for(tEXtMAP::iterator j = key_value_pair.begin(); j != key_value_pair.end(); j++) {
1108
0
          std::string key = (*j).first;
1109
0
          std::string value = (*j).second;
1110
0
          mng_SetKeyValue(FIMD_COMMENTS, dib, key.c_str(), value.c_str());
1111
0
        }
1112
0
      }
1113
0
    }
1114
      
1115
0
    return dib;
1116
1117
0
  } catch(const char *text) {
1118
0
    FreeImage_CloseMemory(hJpegMemory);
1119
0
    FreeImage_CloseMemory(hPngMemory);
1120
0
    FreeImage_CloseMemory(hIDATMemory);
1121
0
    free(mChunk);
1122
0
    free(PLTE_file_chunk);
1123
0
    FreeImage_Unload(dib);
1124
0
    FreeImage_Unload(dib_alpha);
1125
0
    if(text) {
1126
0
      FreeImage_OutputMessageProc(format_id, text);
1127
0
    }
1128
0
    return NULL;
1129
0
  }
1130
0
}
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 
1144
0
mng_WriteJNG(int format_id, FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int flags) {
1145
0
  DWORD jng_width = 0;
1146
0
  DWORD jng_height = 0;
1147
0
  BYTE jng_color_type = 0;
1148
0
  BYTE jng_image_sample_depth = 8;
1149
0
  BYTE jng_image_compression_method = 8;  //  8: ISO-10918-1 Huffman-coded baseline JPEG.
1150
0
  BYTE jng_image_interlace_method = 0;
1151
1152
0
  BYTE jng_alpha_sample_depth = 0;
1153
0
  BYTE jng_alpha_compression_method = 0;
1154
0
  BYTE jng_alpha_filter_method = 0;
1155
0
  BYTE jng_alpha_interlace_method = 0;
1156
1157
0
  BYTE buffer[16];
1158
1159
0
  FIMEMORY *hJngMemory = NULL;
1160
0
  FIMEMORY *hJpegMemory = NULL;
1161
0
  FIMEMORY *hPngMemory = NULL;
1162
1163
0
  FIBITMAP *dib_rgb = NULL;
1164
0
  FIBITMAP *dib_alpha = NULL;
1165
1166
0
  if(!dib || (FreeImage_GetImageType(dib) != FIT_BITMAP)) {
1167
0
    return FALSE;
1168
0
  }
1169
1170
0
  unsigned bpp = FreeImage_GetBPP(dib);
1171
1172
0
  switch(bpp) {
1173
0
    case 8:
1174
0
      if(FreeImage_GetColorType(dib) == FIC_MINISBLACK) {
1175
0
        dib_rgb = dib;
1176
0
        jng_color_type = MNG_COLORTYPE_JPEGGRAY;
1177
0
      } 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
0
        dib_rgb = dib;
1181
0
        jng_color_type = MNG_COLORTYPE_JPEGCOLOR;
1182
1183
0
      }
1184
0
      break;
1185
0
    case 24:
1186
0
      dib_rgb = dib;
1187
0
      jng_color_type = MNG_COLORTYPE_JPEGCOLOR;
1188
0
      break;
1189
0
    case 32:
1190
0
      dib_rgb = FreeImage_ConvertTo24Bits(dib);
1191
0
      jng_color_type = MNG_COLORTYPE_JPEGCOLORA;
1192
0
      jng_alpha_sample_depth = 8;
1193
0
      break;
1194
0
    default:
1195
0
      return FALSE;
1196
0
  }
1197
1198
0
  jng_width = (DWORD)FreeImage_GetWidth(dib);
1199
0
  jng_height = (DWORD)FreeImage_GetHeight(dib);
1200
1201
0
  try {
1202
0
    hJngMemory = FreeImage_OpenMemory();
1203
1204
    // --- write JNG file signature ---
1205
0
    FreeImage_WriteMemory(g_jng_signature, 1, 8, hJngMemory);
1206
1207
    // --- write a JHDR chunk ---
1208
0
    SwapLong(&jng_width);
1209
0
    SwapLong(&jng_height);
1210
0
    memcpy(&buffer[0], &jng_width, 4);
1211
0
    memcpy(&buffer[4], &jng_height, 4);
1212
0
    SwapLong(&jng_width);
1213
0
    SwapLong(&jng_height);
1214
0
    buffer[8] = jng_color_type;
1215
0
    buffer[9] = jng_image_sample_depth;
1216
0
    buffer[10] = jng_image_compression_method;
1217
0
    buffer[11] = jng_image_interlace_method;
1218
0
    buffer[12] = jng_alpha_sample_depth;
1219
0
    buffer[13] = jng_alpha_compression_method;
1220
0
    buffer[14] = jng_alpha_filter_method;
1221
0
    buffer[15] = jng_alpha_interlace_method;
1222
0
    mng_WriteChunk(mng_JHDR, &buffer[0], 16, hJngMemory);
1223
1224
    // --- write a sequence of JDAT chunks ---
1225
0
    hJpegMemory = FreeImage_OpenMemory();
1226
0
    flags |= JPEG_BASELINE;
1227
0
    if(!FreeImage_SaveToMemory(FIF_JPEG, dib_rgb, hJpegMemory, flags)) {
1228
0
      throw (const char*)NULL;
1229
0
    }
1230
0
    if(dib_rgb != dib) {
1231
0
      FreeImage_Unload(dib_rgb);
1232
0
      dib_rgb = NULL;
1233
0
    }
1234
0
    {
1235
0
      BYTE *jpeg_data = NULL;
1236
0
      DWORD size_in_bytes = 0;
1237
      
1238
      // get a pointer to the stream buffer
1239
0
      FreeImage_AcquireMemory(hJpegMemory, &jpeg_data, &size_in_bytes);
1240
      // write chunks
1241
0
      for(DWORD k = 0; k < size_in_bytes;) {
1242
0
        DWORD bytes_left = size_in_bytes - k;
1243
0
        DWORD chunk_size = MIN(JPEG_CHUNK_SIZE, bytes_left);
1244
0
        mng_WriteChunk(mng_JDAT, &jpeg_data[k], chunk_size, hJngMemory);
1245
0
        k += chunk_size;
1246
0
      }
1247
0
    }
1248
0
    FreeImage_CloseMemory(hJpegMemory);
1249
0
    hJpegMemory = NULL;
1250
1251
    // --- write alpha layer as a sequence of IDAT chunk ---
1252
0
    if((bpp == 32) && (jng_color_type == MNG_COLORTYPE_JPEGCOLORA)) {
1253
0
      dib_alpha = FreeImage_GetChannel(dib, FICC_ALPHA);
1254
1255
0
      hPngMemory = FreeImage_OpenMemory();
1256
0
      if(!FreeImage_SaveToMemory(FIF_PNG, dib_alpha, hPngMemory, PNG_DEFAULT)) {
1257
0
        throw (const char*)NULL;
1258
0
      }
1259
0
      FreeImage_Unload(dib_alpha);
1260
0
      dib_alpha = NULL;
1261
      // get the IDAT chunk
1262
0
      {   
1263
0
        BOOL bResult = FALSE;
1264
0
        DWORD start_pos = 0;
1265
0
        DWORD next_pos = 0;
1266
0
        long offset = 8;
1267
        
1268
0
        do {
1269
          // find the next IDAT chunk from 'offset' position
1270
0
          bResult = mng_FindChunk(hPngMemory, mng_IDAT, offset, &start_pos, &next_pos);
1271
0
          if(!bResult) break;
1272
          
1273
0
          BYTE *png_data = NULL;
1274
0
          DWORD size_in_bytes = 0;
1275
          
1276
          // get a pointer to the stream buffer
1277
0
          FreeImage_AcquireMemory(hPngMemory, &png_data, &size_in_bytes);
1278
          // write the IDAT chunk
1279
0
          mng_WriteChunk(mng_IDAT, &png_data[start_pos+8], next_pos - start_pos - 12, hJngMemory);
1280
1281
0
          offset = next_pos;
1282
1283
0
        } while(bResult);
1284
0
      }
1285
1286
0
      FreeImage_CloseMemory(hPngMemory);
1287
0
      hPngMemory = NULL;
1288
0
    }
1289
1290
    // --- write a IEND chunk ---
1291
0
    mng_WriteChunk(mng_IEND, NULL, 0, hJngMemory);
1292
1293
    // write the JNG on output stream
1294
0
    {
1295
0
      BYTE *jng_data = NULL;
1296
0
      DWORD size_in_bytes = 0;
1297
0
      FreeImage_AcquireMemory(hJngMemory, &jng_data, &size_in_bytes);
1298
0
      io->write_proc(jng_data, 1, size_in_bytes, handle);     
1299
0
    }
1300
1301
0
    FreeImage_CloseMemory(hJngMemory);
1302
0
    FreeImage_CloseMemory(hJpegMemory);
1303
0
    FreeImage_CloseMemory(hPngMemory);
1304
1305
0
    return TRUE;
1306
1307
0
  } catch(const char *text) {
1308
0
    FreeImage_CloseMemory(hJngMemory);
1309
0
    FreeImage_CloseMemory(hJpegMemory);
1310
0
    FreeImage_CloseMemory(hPngMemory);
1311
0
    if(dib_rgb && (dib_rgb != dib)) {
1312
0
      FreeImage_Unload(dib_rgb);
1313
0
    }
1314
0
    FreeImage_Unload(dib_alpha);
1315
0
    if(text) {
1316
0
      FreeImage_OutputMessageProc(format_id, text);
1317
0
    }
1318
0
    return FALSE;
1319
0
  }
1320
0
}