// ========================================================== // MNG / JNG helpers // // Design and implementation by // - Hervé Drolon (drolon@infonie.fr) // // This file is part of FreeImage 3 // // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER // THIS DISCLAIMER. // // Use at your own risk! // ========================================================== #include "FreeImage.h" #include "Utilities.h" /** References http://www.libpng.org/pub/mng/spec/jng.html http://www.w3.org/TR/PNG/ http://libpng.org/pub/mng/spec/ */ // -------------------------------------------------------------------------- #define MNG_INCLUDE_JNG #ifdef MNG_INCLUDE_JNG #define MNG_COLORTYPE_JPEGGRAY 8 /* JHDR */ #define MNG_COLORTYPE_JPEGCOLOR 10 #define MNG_COLORTYPE_JPEGGRAYA 12 #define MNG_COLORTYPE_JPEGCOLORA 14 #define MNG_BITDEPTH_JPEG8 8 /* JHDR */ #define MNG_BITDEPTH_JPEG12 12 #define MNG_BITDEPTH_JPEG8AND12 20 #define MNG_COMPRESSION_BASELINEJPEG 8 /* JHDR */ #define MNG_INTERLACE_SEQUENTIAL 0 /* JHDR */ #define MNG_INTERLACE_PROGRESSIVE 8 #endif /* MNG_INCLUDE_JNG */ // -------------------------------------------------------------------------- #define JNG_SUPPORTED /** Size of a JDAT chunk on writing */ const DWORD JPEG_CHUNK_SIZE = 8192; /** PNG signature */ static const BYTE g_png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; /** JNG signature */ static const BYTE g_jng_signature[8] = { 139, 74, 78, 71, 13, 10, 26, 10 }; // -------------------------------------------------------------------------- /** Chunk type converted to enum */ enum eChunckType { UNKNOWN_CHUNCK, MHDR, BACK, BASI, CLIP, CLON, DEFI, DHDR, DISC, ENDL, FRAM, IEND, IHDR, JHDR, LOOP, MAGN, MEND, MOVE, PAST, PLTE, SAVE, SEEK, SHOW, TERM, bKGD, cHRM, gAMA, iCCP, nEED, pHYg, vpAg, pHYs, sBIT, sRGB, tRNS, IDAT, JDAT, JDAA, JdAA, JSEP, oFFs, hIST, iTXt, sPLT, sTER, tEXt, tIME, zTXt }; /** Helper for map where value is a pointer to a string. Used to store tEXt metadata. */ typedef std::map tEXtMAP; // -------------------------------------------------------------------------- /* Constant strings for known chunk types. If you need to add a chunk, add a string holding the name here. To make the code more portable, we use ASCII numbers like this, not characters. */ static BYTE mng_MHDR[5]={ 77, 72, 68, 82, (BYTE) '\0'}; static BYTE mng_BACK[5]={ 66, 65, 67, 75, (BYTE) '\0'}; static BYTE mng_BASI[5]={ 66, 65, 83, 73, (BYTE) '\0'}; static BYTE mng_CLIP[5]={ 67, 76, 73, 80, (BYTE) '\0'}; static BYTE mng_CLON[5]={ 67, 76, 79, 78, (BYTE) '\0'}; static BYTE mng_DEFI[5]={ 68, 69, 70, 73, (BYTE) '\0'}; static BYTE mng_DHDR[5]={ 68, 72, 68, 82, (BYTE) '\0'}; static BYTE mng_DISC[5]={ 68, 73, 83, 67, (BYTE) '\0'}; static BYTE mng_ENDL[5]={ 69, 78, 68, 76, (BYTE) '\0'}; static BYTE mng_FRAM[5]={ 70, 82, 65, 77, (BYTE) '\0'}; static BYTE mng_IEND[5]={ 73, 69, 78, 68, (BYTE) '\0'}; static BYTE mng_IHDR[5]={ 73, 72, 68, 82, (BYTE) '\0'}; static BYTE mng_JHDR[5]={ 74, 72, 68, 82, (BYTE) '\0'}; static BYTE mng_LOOP[5]={ 76, 79, 79, 80, (BYTE) '\0'}; static BYTE mng_MAGN[5]={ 77, 65, 71, 78, (BYTE) '\0'}; static BYTE mng_MEND[5]={ 77, 69, 78, 68, (BYTE) '\0'}; static BYTE mng_MOVE[5]={ 77, 79, 86, 69, (BYTE) '\0'}; static BYTE mng_PAST[5]={ 80, 65, 83, 84, (BYTE) '\0'}; static BYTE mng_PLTE[5]={ 80, 76, 84, 69, (BYTE) '\0'}; static BYTE mng_SAVE[5]={ 83, 65, 86, 69, (BYTE) '\0'}; static BYTE mng_SEEK[5]={ 83, 69, 69, 75, (BYTE) '\0'}; static BYTE mng_SHOW[5]={ 83, 72, 79, 87, (BYTE) '\0'}; static BYTE mng_TERM[5]={ 84, 69, 82, 77, (BYTE) '\0'}; static BYTE mng_bKGD[5]={ 98, 75, 71, 68, (BYTE) '\0'}; static BYTE mng_cHRM[5]={ 99, 72, 82, 77, (BYTE) '\0'}; static BYTE mng_gAMA[5]={103, 65, 77, 65, (BYTE) '\0'}; static BYTE mng_iCCP[5]={105, 67, 67, 80, (BYTE) '\0'}; static BYTE mng_nEED[5]={110, 69, 69, 68, (BYTE) '\0'}; static BYTE mng_pHYg[5]={112, 72, 89, 103, (BYTE) '\0'}; static BYTE mng_vpAg[5]={118, 112, 65, 103, (BYTE) '\0'}; static BYTE mng_pHYs[5]={112, 72, 89, 115, (BYTE) '\0'}; static BYTE mng_sBIT[5]={115, 66, 73, 84, (BYTE) '\0'}; static BYTE mng_sRGB[5]={115, 82, 71, 66, (BYTE) '\0'}; static BYTE mng_tRNS[5]={116, 82, 78, 83, (BYTE) '\0'}; #if defined(JNG_SUPPORTED) static BYTE mng_IDAT[5]={ 73, 68, 65, 84, (BYTE) '\0'}; static BYTE mng_JDAT[5]={ 74, 68, 65, 84, (BYTE) '\0'}; static BYTE mng_JDAA[5]={ 74, 68, 65, 65, (BYTE) '\0'}; static BYTE mng_JdAA[5]={ 74, 100, 65, 65, (BYTE) '\0'}; static BYTE mng_JSEP[5]={ 74, 83, 69, 80, (BYTE) '\0'}; static BYTE mng_oFFs[5]={111, 70, 70, 115, (BYTE) '\0'}; #endif static BYTE mng_hIST[5]={104, 73, 83, 84, (BYTE) '\0'}; static BYTE mng_iTXt[5]={105, 84, 88, 116, (BYTE) '\0'}; static BYTE mng_sPLT[5]={115, 80, 76, 84, (BYTE) '\0'}; static BYTE mng_sTER[5]={115, 84, 69, 82, (BYTE) '\0'}; static BYTE mng_tEXt[5]={116, 69, 88, 116, (BYTE) '\0'}; static BYTE mng_tIME[5]={116, 73, 77, 69, (BYTE) '\0'}; static BYTE mng_zTXt[5]={122, 84, 88, 116, (BYTE) '\0'}; // -------------------------------------------------------------------------- /** Convert a chunk name to a unique ID */ static eChunckType mng_GetChunckType(const BYTE *mChunkName) { if(memcmp(mChunkName, mng_MHDR, 4) == 0) { return MHDR; } if(memcmp(mChunkName, mng_LOOP, 4) == 0) { return LOOP; } if(memcmp(mChunkName, mng_DEFI, 4) == 0) { return DEFI; } if(memcmp(mChunkName, mng_PLTE, 4) == 0) { return PLTE; } if(memcmp(mChunkName, mng_tRNS, 4) == 0) { return tRNS; } if(memcmp(mChunkName, mng_IHDR, 4) == 0) { return IHDR; } if(memcmp(mChunkName, mng_JHDR, 4) == 0) { return JHDR; } if(memcmp(mChunkName, mng_MEND, 4) == 0) { return MEND; } if(memcmp(mChunkName, mng_IEND, 4) == 0) { return IEND; } if(memcmp(mChunkName, mng_JDAT, 4) == 0) { return JDAT; } if(memcmp(mChunkName, mng_IDAT, 4) == 0) { return IDAT; } if(memcmp(mChunkName, mng_JDAA, 4) == 0) { return JDAA; } if(memcmp(mChunkName, mng_gAMA, 4) == 0) { return gAMA; } if(memcmp(mChunkName, mng_pHYs, 4) == 0) { return pHYs; } if(memcmp(mChunkName, mng_bKGD, 4) == 0) { return bKGD; } if(memcmp(mChunkName, mng_tEXt, 4) == 0) { return tEXt; } return UNKNOWN_CHUNCK; } inline void mng_SwapShort(WORD *sp) { #ifndef FREEIMAGE_BIGENDIAN SwapShort(sp); #endif } inline void mng_SwapLong(DWORD *lp) { #ifndef FREEIMAGE_BIGENDIAN SwapLong(lp); #endif } /** Returns the size, in bytes, of a FreeImageIO stream, from the current position. */ static long mng_LOF(FreeImageIO *io, fi_handle handle) { long start_pos = io->tell_proc(handle); io->seek_proc(handle, 0, SEEK_END); long file_length = io->tell_proc(handle); io->seek_proc(handle, start_pos, SEEK_SET); return file_length; } /** Count the number of bytes in a PNG stream, from IHDR to IEND. If successful, the stream position, as given by io->tell_proc(handle), should be the end of the PNG stream at the return of the function. @param io @param handle @param inPos @param m_TotalBytesOfChunks @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL mng_CountPNGChunks(FreeImageIO *io, fi_handle handle, long inPos, unsigned *m_TotalBytesOfChunks) { long mLOF; long mPos; BOOL mEnd = FALSE; DWORD mLength = 0; BYTE mChunkName[5]; *m_TotalBytesOfChunks = 0; // get the length of the file mLOF = mng_LOF(io, handle); // go to the start of the file io->seek_proc(handle, inPos, SEEK_SET); try { // parse chunks while(mEnd == FALSE) { // chunk length mPos = io->tell_proc(handle); if(mPos + 4 > mLOF) { throw(1); } io->read_proc(&mLength, 1, 4, handle); mng_SwapLong(&mLength); // chunk name mPos = io->tell_proc(handle); if(mPos + 4 > mLOF) { throw(1); } io->read_proc(&mChunkName[0], 1, 4, handle); mChunkName[4] = '\0'; // go to next chunk mPos = io->tell_proc(handle); // 4 = size of the CRC if(mPos + (long)mLength + 4 > mLOF) { throw(1); } io->seek_proc(handle, mLength + 4, SEEK_CUR); switch( mng_GetChunckType(mChunkName) ) { case IHDR: if(mLength != 13) { throw(1); } break; case IEND: mEnd = TRUE; // the length below includes 4 bytes CRC, but no bytes for Length *m_TotalBytesOfChunks = io->tell_proc(handle) - inPos; break; case UNKNOWN_CHUNCK: default: break; } } // while(!mEnd) return TRUE; } catch(int) { return FALSE; } } /** Retrieve the position of a chunk in a PNG stream @param hPngMemory PNG stream handle @param chunk_name Name of the chunk to be found @param offset Start of the search in the stream @param start_pos [returned value] Start position of the chunk @param next_pos [returned value] Start position of the next chunk @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL mng_FindChunk(FIMEMORY *hPngMemory, BYTE *chunk_name, long offset, DWORD *start_pos, DWORD *next_pos) { DWORD mLength = 0; BYTE *data = NULL; DWORD size_in_bytes = 0; *start_pos = 0; *next_pos = 0; // get a pointer to the stream buffer FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes); if(!(data && size_in_bytes) || (size_in_bytes < 20) || (size_in_bytes - offset < 20)) { // not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes) return FALSE; } try { // skip the signature and/or any following chunk(s) DWORD chunk_pos = offset; while(1) { // get chunk length if(chunk_pos + 4 > size_in_bytes) { break; } memcpy(&mLength, &data[chunk_pos], 4); mng_SwapLong(&mLength); chunk_pos += 4; const DWORD next_chunk_pos = chunk_pos + 4 + mLength + 4; if(next_chunk_pos > size_in_bytes) { break; } // get chunk name if(memcmp(&data[chunk_pos], chunk_name, 4) == 0) { chunk_pos -= 4; // found chunk *start_pos = chunk_pos; *next_pos = next_chunk_pos; return TRUE; } chunk_pos = next_chunk_pos; } return FALSE; } catch(int) { return FALSE; } } /** Remove a chunk located at (start_pos, next_pos) in the PNG stream @param hPngMemory PNG stream handle @param start_pos Start position of the chunk @param next_pos Start position of the next chunk @return Returns TRUE if successfull, returns FALSE otherwise */ static BOOL mng_CopyRemoveChunks(FIMEMORY *hPngMemory, DWORD start_pos, DWORD next_pos) { BYTE *data = NULL; DWORD size_in_bytes = 0; // length of the chunk to remove DWORD chunk_length = next_pos - start_pos; if(chunk_length == 0) { return TRUE; } // get a pointer to the stream buffer FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes); if(!(data && size_in_bytes) || (size_in_bytes < 20) || (chunk_length >= size_in_bytes)) { // not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes) return FALSE; } // new file length unsigned buffer_size = size_in_bytes + chunk_length; BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE)); if(!buffer) { return FALSE; } memcpy(&buffer[0], &data[0], start_pos); memcpy(&buffer[start_pos], &data[next_pos], size_in_bytes - next_pos); // seek to the start of the stream FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET); // re-write the stream FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory); free(buffer); return TRUE; } /** Insert a chunk just before the inNextChunkName chunk @param hPngMemory PNG stream handle @param start_pos Start position of the inNextChunkName chunk @param next_pos Start position of the next chunk @return Returns TRUE if successfull, returns FALSE otherwise */ static BOOL mng_CopyInsertChunks(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, DWORD inChunkLength, DWORD start_pos, DWORD next_pos) { BYTE *data = NULL; DWORD size_in_bytes = 0; // length of the chunk to check DWORD chunk_length = next_pos - start_pos; if(chunk_length == 0) { return TRUE; } // get a pointer to the stream buffer FreeImage_AcquireMemory(hPngMemory, &data, &size_in_bytes); if(!(data && size_in_bytes) || (size_in_bytes < 20) || (chunk_length >= size_in_bytes)) { // not enough space to read a signature(8 bytes) + a chunk(at least 12 bytes) return FALSE; } // new file length unsigned buffer_size = inChunkLength + size_in_bytes; BYTE *buffer = (BYTE*)malloc(buffer_size * sizeof(BYTE)); if(!buffer) { return FALSE; } unsigned p = 0; memcpy(&buffer[p], &data[0], start_pos); p += start_pos; memcpy(&buffer[p], inInsertChunk, inChunkLength); p += inChunkLength; memcpy(&buffer[p], &data[start_pos], size_in_bytes - start_pos); // seek to the start of the stream FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET); // re-write the stream FreeImage_WriteMemory(buffer, 1, buffer_size, hPngMemory); free(buffer); return TRUE; } static BOOL mng_RemoveChunk(FIMEMORY *hPngMemory, BYTE *chunk_name) { BOOL bResult = FALSE; DWORD start_pos = 0; DWORD next_pos = 0; bResult = mng_FindChunk(hPngMemory, chunk_name, 8, &start_pos, &next_pos); if(!bResult) { return FALSE; } bResult = mng_CopyRemoveChunks(hPngMemory, start_pos, next_pos); if(!bResult) { return FALSE; } return TRUE; } static BOOL mng_InsertChunk(FIMEMORY *hPngMemory, BYTE *inNextChunkName, BYTE *inInsertChunk, unsigned chunk_length) { BOOL bResult = FALSE; DWORD start_pos = 0; DWORD next_pos = 0; bResult = mng_FindChunk(hPngMemory, inNextChunkName, 8, &start_pos, &next_pos); if(!bResult) { return FALSE; } bResult = mng_CopyInsertChunks(hPngMemory, inNextChunkName, inInsertChunk, chunk_length, start_pos, next_pos); if(!bResult) { return FALSE; } return TRUE; } static FIBITMAP* mng_LoadFromMemoryHandle(FIMEMORY *hmem, int flags = 0) { long offset = 0; FIBITMAP *dib = NULL; if(hmem) { // seek to the start of the stream FreeImage_SeekMemory(hmem, offset, SEEK_SET); // check the file signature and deduce its format // (the second argument is currently not used by FreeImage) FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0); if(fif != FIF_UNKNOWN) { dib = FreeImage_LoadFromMemory(fif, hmem, flags); } } return dib; } /** Write a chunk in a PNG stream from the current position. @param chunk_name Name of the chunk @param chunk_data Chunk array @param length Chunk length @param hPngMemory PNG stream handle */ static void mng_WriteChunk(BYTE *chunk_name, BYTE *chunk_data, DWORD length, FIMEMORY *hPngMemory) { DWORD crc_file = 0; // write a PNG chunk ... // - length mng_SwapLong(&length); FreeImage_WriteMemory(&length, 1, 4, hPngMemory); mng_SwapLong(&length); // - chunk name FreeImage_WriteMemory(chunk_name, 1, 4, hPngMemory); if(chunk_data && length) { // - chunk data FreeImage_WriteMemory(chunk_data, 1, length, hPngMemory); // - crc crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4); crc_file = FreeImage_ZLibCRC32(crc_file, chunk_data, length); mng_SwapLong(&crc_file); FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory); } else { // - crc crc_file = FreeImage_ZLibCRC32(0, chunk_name, 4); mng_SwapLong(&crc_file); FreeImage_WriteMemory(&crc_file, 1, 4, hPngMemory); } } /** Wrap a IDAT chunk as a PNG stream. The stream has the structure { g_png_signature, IHDR, IDAT, IEND } The image is assumed to be a greyscale image. @param jng_width Image width @param jng_height Image height @param jng_alpha_sample_depth Bits per pixel @param mChunk PNG grayscale IDAT format @param mLength IDAT chunk length @param hPngMemory Output memory stream */ static void mng_WritePNGStream(DWORD jng_width, DWORD jng_height, BYTE jng_alpha_sample_depth, BYTE *mChunk, DWORD mLength, FIMEMORY *hPngMemory) { // PNG grayscale IDAT format BYTE data[14]; // wrap the IDAT chunk as a PNG stream // write PNG file signature FreeImage_WriteMemory(g_png_signature, 1, 8, hPngMemory); // write a IHDR chunk ... /* The IHDR chunk must appear FIRST. It contains: Width: 4 bytes Height: 4 bytes Bit depth: 1 byte Color type: 1 byte Compression method: 1 byte Filter method: 1 byte Interlace method: 1 byte */ // - chunk data mng_SwapLong(&jng_width); mng_SwapLong(&jng_height); memcpy(&data[0], &jng_width, 4); memcpy(&data[4], &jng_height, 4); mng_SwapLong(&jng_width); mng_SwapLong(&jng_height); data[8] = jng_alpha_sample_depth; data[9] = 0; // color_type gray (jng_color_type) data[10] = 0; // compression method 0 (jng_alpha_compression_method) data[11] = 0; // filter_method 0 (jng_alpha_filter_method) data[12] = 0; // interlace_method 0 (jng_alpha_interlace_method) mng_WriteChunk(mng_IHDR, &data[0], 13, hPngMemory); // write a IDAT chunk ... mng_WriteChunk(mng_IDAT, mChunk, mLength, hPngMemory); // write a IEND chunk ... mng_WriteChunk(mng_IEND, NULL, 0, hPngMemory); } // -------------------------------------------------------------------------- /** Build and set a FITAG whose type is FIDT_ASCII. The tag must be destroyed by the caller using FreeImage_DeleteTag. @param model Metadata model to be filled @param dib Image to be filled @param key Tag key @param value Tag value @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL mng_SetKeyValue(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, const char *value) { if(!dib || !key || !value) { return FALSE; } // create a tag FITAG *tag = FreeImage_CreateTag(); if(tag) { BOOL bSuccess = TRUE; // fill the tag DWORD tag_length = (DWORD)(strlen(value) + 1); bSuccess &= FreeImage_SetTagKey(tag, key); bSuccess &= FreeImage_SetTagLength(tag, tag_length); bSuccess &= FreeImage_SetTagCount(tag, tag_length); bSuccess &= FreeImage_SetTagType(tag, FIDT_ASCII); bSuccess &= FreeImage_SetTagValue(tag, value); if(bSuccess) { // set the tag FreeImage_SetMetadata(model, dib, FreeImage_GetTagKey(tag), tag); } FreeImage_DeleteTag(tag); return bSuccess; } return FALSE; } /** Read a tEXt chunk and extract the key/value pair. @param key_value_pair [returned value] Array of key/value pairs @param mChunk Chunk data @param mLength Chunk length @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL mng_SetMetadata_tEXt(tEXtMAP &key_value_pair, const BYTE *mChunk, DWORD mLength) { std::string key; std::string value; BYTE *buffer = (BYTE*)malloc(mLength * sizeof(BYTE)); if(!buffer) { return FALSE; } DWORD pos = 0; memset(buffer, 0, mLength * sizeof(BYTE)); for(DWORD i = 0; i < mLength; i++) { buffer[pos++] = mChunk[i]; if(mChunk[i] == '\0') { if(key.size() == 0) { key = (char*)buffer; pos = 0; memset(buffer, 0, mLength * sizeof(BYTE)); } else { break; } } } value = (char*)buffer; free(buffer); key_value_pair[key] = value; return TRUE; } // -------------------------------------------------------------------------- /** Load a FIBITMAP from a MNG or a JNG stream @param format_id ID of the caller @param io Stream i/o functions @param handle Stream handle @param Offset Start of the first chunk @param flags Loading flags @return Returns a dib if successful, returns NULL otherwise */ FIBITMAP* mng_ReadChunks(int format_id, FreeImageIO *io, fi_handle handle, long Offset, int flags = 0) { DWORD mLength = 0; BYTE mChunkName[5]; BYTE *mChunk = NULL; DWORD crc_file; long LastOffset; long mOrigPos; BYTE *PLTE_file_chunk = NULL; // whole PLTE chunk (lentgh, name, array, crc) DWORD PLTE_file_size = 0; // size of PLTE chunk BOOL m_HasGlobalPalette = FALSE; // may turn to TRUE in PLTE chunk unsigned m_TotalBytesOfChunks = 0; FIBITMAP *dib = NULL; FIBITMAP *dib_alpha = NULL; FIMEMORY *hJpegMemory = NULL; FIMEMORY *hPngMemory = NULL; FIMEMORY *hIDATMemory = NULL; // --- DWORD jng_width = 0; DWORD jng_height = 0; BYTE jng_color_type = 0; BYTE jng_image_sample_depth = 0; BYTE jng_image_compression_method = 0; BYTE jng_alpha_sample_depth = 0; BYTE jng_alpha_compression_method = 0; BYTE jng_alpha_filter_method = 0; BYTE jng_alpha_interlace_method = 0; DWORD mng_frame_width = 0; DWORD mng_frame_height = 0; DWORD mng_ticks_per_second = 0; DWORD mng_nominal_layer_count = 0; DWORD mng_nominal_frame_count = 0; DWORD mng_nominal_play_time = 0; DWORD mng_simplicity_profile = 0; DWORD res_x = 2835; // 72 dpi DWORD res_y = 2835; // 72 dpi RGBQUAD rgbBkColor = {0, 0, 0, 0}; WORD bk_red, bk_green, bk_blue; BOOL hasBkColor = FALSE; BOOL mHasIDAT = FALSE; tEXtMAP key_value_pair; // --- BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; // get the file size const long mLOF = mng_LOF(io, handle); // go to the first chunk io->seek_proc(handle, Offset, SEEK_SET); try { BOOL mEnd = FALSE; while(mEnd == FALSE) { // start of the chunk LastOffset = io->tell_proc(handle); // read length mLength = 0; io->read_proc(&mLength, 1, sizeof(mLength), handle); mng_SwapLong(&mLength); // read name io->read_proc(&mChunkName[0], 1, 4, handle); mChunkName[4] = '\0'; if(mLength > 0) { mChunk = (BYTE*)realloc(mChunk, mLength); if(!mChunk) { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName); throw (const char*)NULL; } Offset = io->tell_proc(handle); if(Offset + (long)mLength > mLOF) { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of file", mChunkName); throw (const char*)NULL; } // read chunk io->read_proc(mChunk, 1, mLength, handle); } // read crc io->read_proc(&crc_file, 1, sizeof(crc_file), handle); mng_SwapLong(&crc_file); // check crc DWORD crc_check = FreeImage_ZLibCRC32(0, &mChunkName[0], 4); crc_check = FreeImage_ZLibCRC32(crc_check, mChunk, mLength); if(crc_check != crc_file) { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: bad CRC", mChunkName); throw (const char*)NULL; } switch( mng_GetChunckType(mChunkName) ) { case MHDR: // The MHDR chunk is always first in all MNG datastreams except for those // that consist of a single PNG or JNG datastream with a PNG or JNG signature. if(mLength == 28) { memcpy(&mng_frame_width, &mChunk[0], 4); memcpy(&mng_frame_height, &mChunk[4], 4); memcpy(&mng_ticks_per_second, &mChunk[8], 4); memcpy(&mng_nominal_layer_count, &mChunk[12], 4); memcpy(&mng_nominal_frame_count, &mChunk[16], 4); memcpy(&mng_nominal_play_time, &mChunk[20], 4); memcpy(&mng_simplicity_profile, &mChunk[24], 4); mng_SwapLong(&mng_frame_width); mng_SwapLong(&mng_frame_height); mng_SwapLong(&mng_ticks_per_second); mng_SwapLong(&mng_nominal_layer_count); mng_SwapLong(&mng_nominal_frame_count); mng_SwapLong(&mng_nominal_play_time); mng_SwapLong(&mng_simplicity_profile); } else { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: size is %d instead of 28", mChunkName, mLength); } break; case MEND: mEnd = TRUE; break; case LOOP: case ENDL: break; case DEFI: break; case SAVE: case SEEK: case TERM: break; case BACK: break; // Global "PLTE" and "tRNS" (if any). PNG "PLTE" will be of 0 byte, as it uses global data. case PLTE: // Global m_HasGlobalPalette = TRUE; PLTE_file_size = mLength + 12; // (lentgh, name, array, crc) = (4, 4, mLength, 4) PLTE_file_chunk = (BYTE*)realloc(PLTE_file_chunk, PLTE_file_size); if(!PLTE_file_chunk) { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName); throw (const char*)NULL; } else { mOrigPos = io->tell_proc(handle); // seek to the start of the chunk io->seek_proc(handle, LastOffset, SEEK_SET); // load the whole chunk io->read_proc(PLTE_file_chunk, 1, PLTE_file_size, handle); // go to the start of the next chunk io->seek_proc(handle, mOrigPos, SEEK_SET); } break; case tRNS: // Global break; case IHDR: Offset = LastOffset; // parse the PNG file and get its file size if(mng_CountPNGChunks(io, handle, Offset, &m_TotalBytesOfChunks) == FALSE) { // reach an unexpected end of file mEnd = TRUE; FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: unexpected end of PNG file", mChunkName); break; } // wrap the { IHDR, ..., IEND } chunks as a PNG stream if(hPngMemory == NULL) { hPngMemory = FreeImage_OpenMemory(); } mOrigPos = io->tell_proc(handle); // write PNG file signature FreeImage_SeekMemory(hPngMemory, 0, SEEK_SET); FreeImage_WriteMemory(g_png_signature, 1, 8, hPngMemory); mChunk = (BYTE*)realloc(mChunk, m_TotalBytesOfChunks); if(!mChunk) { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: out of memory", mChunkName); throw (const char*)NULL; } // on calling CountPNGChunks earlier, we were in Offset pos, // go back there io->seek_proc(handle, Offset, SEEK_SET); io->read_proc(mChunk, 1, m_TotalBytesOfChunks, handle); // Put back to original pos io->seek_proc(handle, mOrigPos, SEEK_SET); // write the PNG chunks FreeImage_WriteMemory(mChunk, 1, m_TotalBytesOfChunks, hPngMemory); // plug in global PLTE if local PLTE exists if(m_HasGlobalPalette) { // ensure we remove some local chunks, so that global // "PLTE" can be inserted right before "IDAT". mng_RemoveChunk(hPngMemory, mng_PLTE); mng_RemoveChunk(hPngMemory, mng_tRNS); mng_RemoveChunk(hPngMemory, mng_bKGD); // insert global "PLTE" chunk in its entirety before "IDAT" mng_InsertChunk(hPngMemory, mng_IDAT, PLTE_file_chunk, PLTE_file_size); } if(dib) FreeImage_Unload(dib); dib = mng_LoadFromMemoryHandle(hPngMemory, flags); // stop after the first image mEnd = TRUE; break; case JHDR: if(mLength == 16) { memcpy(&jng_width, &mChunk[0], 4); memcpy(&jng_height, &mChunk[4], 4); mng_SwapLong(&jng_width); mng_SwapLong(&jng_height); jng_color_type = mChunk[8]; jng_image_sample_depth = mChunk[9]; jng_image_compression_method = mChunk[10]; //BYTE jng_image_interlace_method = mChunk[11]; // for debug only jng_alpha_sample_depth = mChunk[12]; jng_alpha_compression_method = mChunk[13]; jng_alpha_filter_method = mChunk[14]; jng_alpha_interlace_method = mChunk[15]; } else { FreeImage_OutputMessageProc(format_id, "Error while parsing %s chunk: invalid chunk length", mChunkName); throw (const char*)NULL; } break; case JDAT: if(hJpegMemory == NULL) { hJpegMemory = FreeImage_OpenMemory(); } // as there may be several JDAT chunks, concatenate them FreeImage_WriteMemory(mChunk, 1, mLength, hJpegMemory); break; case IDAT: if(!header_only && (jng_alpha_compression_method == 0)) { // PNG grayscale IDAT format if(hIDATMemory == NULL) { hIDATMemory = FreeImage_OpenMemory(); mHasIDAT = TRUE; } // as there may be several IDAT chunks, concatenate them FreeImage_WriteMemory(mChunk, 1, mLength, hIDATMemory); } break; case IEND: if(!hJpegMemory) { mEnd = TRUE; break; } // load the JPEG if(dib) { FreeImage_Unload(dib); } dib = mng_LoadFromMemoryHandle(hJpegMemory, flags); // load the PNG alpha layer if(mHasIDAT) { BYTE *data = NULL; DWORD size_in_bytes = 0; // get a pointer to the IDAT buffer FreeImage_AcquireMemory(hIDATMemory, &data, &size_in_bytes); if(data && size_in_bytes) { // wrap the IDAT chunk as a PNG stream if(hPngMemory == NULL) { hPngMemory = FreeImage_OpenMemory(); } mng_WritePNGStream(jng_width, jng_height, jng_alpha_sample_depth, data, size_in_bytes, hPngMemory); // load the PNG if(dib_alpha) { FreeImage_Unload(dib_alpha); } dib_alpha = mng_LoadFromMemoryHandle(hPngMemory, flags); } } // stop the parsing mEnd = TRUE; break; case JDAA: break; case gAMA: break; case pHYs: // unit is pixels per meter memcpy(&res_x, &mChunk[0], 4); mng_SwapLong(&res_x); memcpy(&res_y, &mChunk[4], 4); mng_SwapLong(&res_y); break; case bKGD: memcpy(&bk_red, &mChunk[0], 2); mng_SwapShort(&bk_red); rgbBkColor.rgbRed = (BYTE)bk_red; memcpy(&bk_green, &mChunk[2], 2); mng_SwapShort(&bk_green); rgbBkColor.rgbGreen = (BYTE)bk_green; memcpy(&bk_blue, &mChunk[4], 2); mng_SwapShort(&bk_blue); rgbBkColor.rgbBlue = (BYTE)bk_blue; hasBkColor = TRUE; break; case tEXt: mng_SetMetadata_tEXt(key_value_pair, mChunk, mLength); break; case UNKNOWN_CHUNCK: default: break; } // switch( GetChunckType ) } // while(!mEnd) FreeImage_CloseMemory(hJpegMemory); FreeImage_CloseMemory(hPngMemory); FreeImage_CloseMemory(hIDATMemory); free(mChunk); free(PLTE_file_chunk); // convert to 32-bit if a transparent layer is available if(!header_only && dib_alpha) { FIBITMAP *dst = FreeImage_ConvertTo32Bits(dib); if((FreeImage_GetBPP(dib_alpha) == 8) && (FreeImage_GetImageType(dib_alpha) == FIT_BITMAP)) { FreeImage_SetChannel(dst, dib_alpha, FICC_ALPHA); } else { FIBITMAP *dst_alpha = FreeImage_ConvertTo8Bits(dib_alpha); FreeImage_SetChannel(dst, dst_alpha, FICC_ALPHA); FreeImage_Unload(dst_alpha); } FreeImage_Unload(dib); dib = dst; } FreeImage_Unload(dib_alpha); if(dib) { // set metadata FreeImage_SetDotsPerMeterX(dib, res_x); FreeImage_SetDotsPerMeterY(dib, res_y); if(hasBkColor) { FreeImage_SetBackgroundColor(dib, &rgbBkColor); } if(key_value_pair.size()) { for(tEXtMAP::iterator j = key_value_pair.begin(); j != key_value_pair.end(); j++) { std::string key = (*j).first; std::string value = (*j).second; mng_SetKeyValue(FIMD_COMMENTS, dib, key.c_str(), value.c_str()); } } } return dib; } catch(const char *text) { FreeImage_CloseMemory(hJpegMemory); FreeImage_CloseMemory(hPngMemory); FreeImage_CloseMemory(hIDATMemory); free(mChunk); free(PLTE_file_chunk); FreeImage_Unload(dib); FreeImage_Unload(dib_alpha); if(text) { FreeImage_OutputMessageProc(format_id, text); } return NULL; } } // -------------------------------------------------------------------------- /** Write a FIBITMAP to a JNG stream @param format_id ID of the caller @param io Stream i/o functions @param dib Image to be saved @param handle Stream handle @param flags Saving flags @return Returns TRUE if successful, returns FALSE otherwise */ BOOL mng_WriteJNG(int format_id, FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int flags) { DWORD jng_width = 0; DWORD jng_height = 0; BYTE jng_color_type = 0; BYTE jng_image_sample_depth = 8; BYTE jng_image_compression_method = 8; // 8: ISO-10918-1 Huffman-coded baseline JPEG. BYTE jng_image_interlace_method = 0; BYTE jng_alpha_sample_depth = 0; BYTE jng_alpha_compression_method = 0; BYTE jng_alpha_filter_method = 0; BYTE jng_alpha_interlace_method = 0; BYTE buffer[16]; FIMEMORY *hJngMemory = NULL; FIMEMORY *hJpegMemory = NULL; FIMEMORY *hPngMemory = NULL; FIBITMAP *dib_rgb = NULL; FIBITMAP *dib_alpha = NULL; if(!dib || (FreeImage_GetImageType(dib) != FIT_BITMAP)) { return FALSE; } unsigned bpp = FreeImage_GetBPP(dib); switch(bpp) { case 8: if(FreeImage_GetColorType(dib) == FIC_MINISBLACK) { dib_rgb = dib; jng_color_type = MNG_COLORTYPE_JPEGGRAY; } else { // JPEG plugin will convert other types (FIC_MINISWHITE, FIC_PALETTE) to 24-bit on the fly //dib_rgb = FreeImage_ConvertTo24Bits(dib); dib_rgb = dib; jng_color_type = MNG_COLORTYPE_JPEGCOLOR; } break; case 24: dib_rgb = dib; jng_color_type = MNG_COLORTYPE_JPEGCOLOR; break; case 32: dib_rgb = FreeImage_ConvertTo24Bits(dib); jng_color_type = MNG_COLORTYPE_JPEGCOLORA; jng_alpha_sample_depth = 8; break; default: return FALSE; } jng_width = (DWORD)FreeImage_GetWidth(dib); jng_height = (DWORD)FreeImage_GetHeight(dib); try { hJngMemory = FreeImage_OpenMemory(); // --- write JNG file signature --- FreeImage_WriteMemory(g_jng_signature, 1, 8, hJngMemory); // --- write a JHDR chunk --- SwapLong(&jng_width); SwapLong(&jng_height); memcpy(&buffer[0], &jng_width, 4); memcpy(&buffer[4], &jng_height, 4); SwapLong(&jng_width); SwapLong(&jng_height); buffer[8] = jng_color_type; buffer[9] = jng_image_sample_depth; buffer[10] = jng_image_compression_method; buffer[11] = jng_image_interlace_method; buffer[12] = jng_alpha_sample_depth; buffer[13] = jng_alpha_compression_method; buffer[14] = jng_alpha_filter_method; buffer[15] = jng_alpha_interlace_method; mng_WriteChunk(mng_JHDR, &buffer[0], 16, hJngMemory); // --- write a sequence of JDAT chunks --- hJpegMemory = FreeImage_OpenMemory(); flags |= JPEG_BASELINE; if(!FreeImage_SaveToMemory(FIF_JPEG, dib_rgb, hJpegMemory, flags)) { throw (const char*)NULL; } if(dib_rgb != dib) { FreeImage_Unload(dib_rgb); dib_rgb = NULL; } { BYTE *jpeg_data = NULL; DWORD size_in_bytes = 0; // get a pointer to the stream buffer FreeImage_AcquireMemory(hJpegMemory, &jpeg_data, &size_in_bytes); // write chunks for(DWORD k = 0; k < size_in_bytes;) { DWORD bytes_left = size_in_bytes - k; DWORD chunk_size = MIN(JPEG_CHUNK_SIZE, bytes_left); mng_WriteChunk(mng_JDAT, &jpeg_data[k], chunk_size, hJngMemory); k += chunk_size; } } FreeImage_CloseMemory(hJpegMemory); hJpegMemory = NULL; // --- write alpha layer as a sequence of IDAT chunk --- if((bpp == 32) && (jng_color_type == MNG_COLORTYPE_JPEGCOLORA)) { dib_alpha = FreeImage_GetChannel(dib, FICC_ALPHA); hPngMemory = FreeImage_OpenMemory(); if(!FreeImage_SaveToMemory(FIF_PNG, dib_alpha, hPngMemory, PNG_DEFAULT)) { throw (const char*)NULL; } FreeImage_Unload(dib_alpha); dib_alpha = NULL; // get the IDAT chunk { BOOL bResult = FALSE; DWORD start_pos = 0; DWORD next_pos = 0; long offset = 8; do { // find the next IDAT chunk from 'offset' position bResult = mng_FindChunk(hPngMemory, mng_IDAT, offset, &start_pos, &next_pos); if(!bResult) break; BYTE *png_data = NULL; DWORD size_in_bytes = 0; // get a pointer to the stream buffer FreeImage_AcquireMemory(hPngMemory, &png_data, &size_in_bytes); // write the IDAT chunk mng_WriteChunk(mng_IDAT, &png_data[start_pos+8], next_pos - start_pos - 12, hJngMemory); offset = next_pos; } while(bResult); } FreeImage_CloseMemory(hPngMemory); hPngMemory = NULL; } // --- write a IEND chunk --- mng_WriteChunk(mng_IEND, NULL, 0, hJngMemory); // write the JNG on output stream { BYTE *jng_data = NULL; DWORD size_in_bytes = 0; FreeImage_AcquireMemory(hJngMemory, &jng_data, &size_in_bytes); io->write_proc(jng_data, 1, size_in_bytes, handle); } FreeImage_CloseMemory(hJngMemory); FreeImage_CloseMemory(hJpegMemory); FreeImage_CloseMemory(hPngMemory); return TRUE; } catch(const char *text) { FreeImage_CloseMemory(hJngMemory); FreeImage_CloseMemory(hJpegMemory); FreeImage_CloseMemory(hPngMemory); if(dib_rgb && (dib_rgb != dib)) { FreeImage_Unload(dib_rgb); } FreeImage_Unload(dib_alpha); if(text) { FreeImage_OutputMessageProc(format_id, text); } return FALSE; } }