// ========================================================== // Multi-Page functions // // Design and implementation by // - Floris van den Berg (flvdberg@wxs.nl) // - Laurent Rocher (rocherl@club-internet.fr) // - Steve Johnson (steve@parisgroup.net) // - Petr Pytelka (pyta@lightcomp.com) // - Hervé Drolon (drolon@infonie.fr) // - Vadim Alexandrov (vadimalexandrov@users.sourceforge.net // - Martin Dyring-Andersen (mda@spamfighter.com) // - Volodymyr Goncharov (volodymyr.goncharov@gmail.com) // // 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! // ========================================================== #ifdef _MSC_VER #pragma warning (disable : 4786) // identifier was truncated to 'number' characters #endif #include "CacheFile.h" #include "FreeImageIO.h" #include "Plugin.h" #include "Utilities.h" #include "FreeImage.h" // ---------------------------------------------------------- enum BlockType { BLOCK_CONTINUEUS, BLOCK_REFERENCE }; // ---------------------------------------------------------- struct BlockTypeS { BlockType m_type; BlockTypeS(BlockType type) : m_type(type) { } virtual ~BlockTypeS() {} }; struct BlockContinueus : public BlockTypeS { int m_start; int m_end; BlockContinueus(int s, int e) : BlockTypeS(BLOCK_CONTINUEUS), m_start(s), m_end(e) { } }; struct BlockReference : public BlockTypeS { int m_reference; int m_size; BlockReference(int r, int size) : BlockTypeS(BLOCK_REFERENCE), m_reference(r), m_size(size) { } }; // ---------------------------------------------------------- typedef std::list BlockList; typedef std::list::iterator BlockListIterator; // ---------------------------------------------------------- FI_STRUCT (MULTIBITMAPHEADER) { PluginNode *node; FREE_IMAGE_FORMAT fif; FreeImageIO *io; fi_handle handle; CacheFile *m_cachefile; std::map locked_pages; BOOL changed; int page_count; BlockList m_blocks; char *m_filename; BOOL read_only; FREE_IMAGE_FORMAT cache_fif; int load_flags; }; // ===================================================================== // Helper functions // ===================================================================== inline void ReplaceExtension(std::string& dst_filename, const std::string& src_filename, const std::string& dst_extension) { size_t lastDot = src_filename.find_last_of('.'); if (lastDot == std::string::npos) { dst_filename = src_filename; dst_filename += "."; dst_filename += dst_extension; } else { dst_filename = src_filename.substr(0, lastDot + 1); dst_filename += dst_extension; } } // ===================================================================== // Internal Multipage functions // ===================================================================== inline MULTIBITMAPHEADER * FreeImage_GetMultiBitmapHeader(FIMULTIBITMAP *bitmap) { return (MULTIBITMAPHEADER *)bitmap->data; } static BlockListIterator DLL_CALLCONV FreeImage_FindBlock(FIMULTIBITMAP *bitmap, int position) { assert(NULL != bitmap); MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); // step 1: find the block that matches the given position int prev_count = 0; int count = 0; BlockListIterator i; BlockTypeS *current_block = NULL; for (i = header->m_blocks.begin(); i != header->m_blocks.end(); ++i) { prev_count = count; switch((*i)->m_type) { case BLOCK_CONTINUEUS : count += ((BlockContinueus *)(*i))->m_end - ((BlockContinueus *)(*i))->m_start + 1; break; case BLOCK_REFERENCE : count++; break; } current_block = *i; if (count > position) break; } // step 2: make sure we found the node. from here it gets a little complicated: // * if the block is there, just return it // * if the block is a series of blocks, split it in max 3 new blocks // and return the splitted block if ((current_block) && (count > position)) { switch(current_block->m_type) { case BLOCK_REFERENCE : return i; case BLOCK_CONTINUEUS : { BlockContinueus *block = (BlockContinueus *)current_block; if (block->m_start != block->m_end) { int item = block->m_start + (position - prev_count); // left part if (item != block->m_start) { BlockContinueus *block_a = new BlockContinueus(block->m_start, item - 1); header->m_blocks.insert(i, (BlockTypeS *)block_a); } // middle part BlockContinueus *block_b = new BlockContinueus(item, item); BlockListIterator block_target = header->m_blocks.insert(i, (BlockTypeS *)block_b); // right part if (item != block->m_end) { BlockContinueus *block_c = new BlockContinueus(item + 1, block->m_end); header->m_blocks.insert(i, (BlockTypeS *)block_c); } // remove the old block that was just splitted header->m_blocks.remove((BlockTypeS *)block); delete block; // return the splitted block return block_target; } return i; } } } // we should never go here ... assert(false); return header->m_blocks.end(); } int DLL_CALLCONV FreeImage_InternalGetPageCount(FIMULTIBITMAP *bitmap) { if (bitmap) { if (((MULTIBITMAPHEADER *)bitmap->data)->handle) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); header->io->seek_proc(header->handle, 0, SEEK_SET); void *data = FreeImage_Open(header->node, header->io, header->handle, TRUE); int page_count = (header->node->m_plugin->pagecount_proc != NULL) ? header->node->m_plugin->pagecount_proc(header->io, header->handle, data) : 1; FreeImage_Close(header->node, header->io, header->handle, data); return page_count; } } return 0; } // ===================================================================== // Multipage functions // ===================================================================== FIMULTIBITMAP * DLL_CALLCONV FreeImage_OpenMultiBitmap(FREE_IMAGE_FORMAT fif, const char *filename, BOOL create_new, BOOL read_only, BOOL keep_cache_in_memory, int flags) { FILE *handle = NULL; try { // sanity check on the parameters if (create_new) { read_only = FALSE; } // retrieve the plugin list to find the node belonging to this plugin PluginList *list = FreeImage_GetPluginList(); if (list) { PluginNode *node = list->FindNodeFromFIF(fif); if (node) { std::auto_ptr io (new FreeImageIO); SetDefaultIO(io.get()); if (!create_new) { handle = fopen(filename, "rb"); if (handle == NULL) { return NULL; } } std::auto_ptr bitmap (new FIMULTIBITMAP); std::auto_ptr header (new MULTIBITMAPHEADER); header->m_filename = new char[strlen(filename) + 1]; strcpy(header->m_filename, filename); header->node = node; header->fif = fif; header->io = io.get (); header->handle = handle; header->changed = FALSE; header->read_only = read_only; header->m_cachefile = NULL; header->cache_fif = fif; header->load_flags = flags; // store the MULTIBITMAPHEADER in the surrounding FIMULTIBITMAP structure bitmap->data = header.get(); // cache the page count header->page_count = FreeImage_InternalGetPageCount(bitmap.get()); // allocate a continueus block to describe the bitmap if (!create_new) { header->m_blocks.push_back((BlockTypeS *)new BlockContinueus(0, header->page_count - 1)); } // set up the cache if (!read_only) { std::string cache_name; ReplaceExtension(cache_name, filename, "ficache"); std::auto_ptr cache_file (new CacheFile(cache_name, keep_cache_in_memory)); if (cache_file->open()) { // we can use release() as std::bad_alloc won't be thrown from here on header->m_cachefile = cache_file.release(); } else { // an error occured ... fclose(handle); return NULL; } } // return the multibitmap // std::bad_alloc won't be thrown from here on header.release(); // now owned by bitmap io.release(); // now owned by bitmap return bitmap.release(); // now owned by caller } } } catch (std::bad_alloc &) { /** @todo report error */ } if (handle) fclose(handle); return NULL; } FIMULTIBITMAP * DLL_CALLCONV FreeImage_OpenMultiBitmapU(FREE_IMAGE_FORMAT fif, const wchar_t *filename, BOOL create_new, BOOL read_only, BOOL keep_cache_in_memory, int flags) { // convert to single character - no national chars in extensions char *extension = (char *)malloc(wcslen(filename)+1); unsigned int i=0; for (; i < wcslen(filename); i++) // convert 16-bit to 8-bit extension[i] = (char)(filename[i] & 0x00FF); // set terminating 0 extension[i]=0; FIMULTIBITMAP *fRet = FreeImage_OpenMultiBitmap(fif, extension, create_new, read_only, keep_cache_in_memory, flags); free(extension); return fRet; } FIMULTIBITMAP * DLL_CALLCONV FreeImage_OpenMultiBitmapFromHandle(FREE_IMAGE_FORMAT fif, FreeImageIO *io, fi_handle handle, int flags) { try { BOOL read_only = FALSE; // modifications (if any) will be stored into the memory cache if (io && handle) { // retrieve the plugin list to find the node belonging to this plugin PluginList *list = FreeImage_GetPluginList(); if (list) { PluginNode *node = list->FindNodeFromFIF(fif); if (node) { std::auto_ptr bitmap (new FIMULTIBITMAP); std::auto_ptr header (new MULTIBITMAPHEADER); std::auto_ptr tmp_io (new FreeImageIO (*io)); header->io = tmp_io.get(); header->m_filename = NULL; header->node = node; header->fif = fif; header->handle = handle; header->changed = FALSE; header->read_only = read_only; header->m_cachefile = NULL; header->cache_fif = fif; header->load_flags = flags; // store the MULTIBITMAPHEADER in the surrounding FIMULTIBITMAP structure bitmap->data = header.get(); // cache the page count header->page_count = FreeImage_InternalGetPageCount(bitmap.get()); // allocate a continueus block to describe the bitmap header->m_blocks.push_back((BlockTypeS *)new BlockContinueus(0, header->page_count - 1)); if (!read_only) { // set up the cache std::auto_ptr cache_file (new CacheFile("", TRUE)); if (cache_file->open()) { header->m_cachefile = cache_file.release(); } } tmp_io.release(); header.release(); return bitmap.release(); } } } } catch (std::bad_alloc &) { /** @todo report error */ } return NULL; } BOOL DLL_CALLCONV FreeImage_SaveMultiBitmapToHandle(FREE_IMAGE_FORMAT fif, FIMULTIBITMAP *bitmap, FreeImageIO *io, fi_handle handle, int flags) { if(!bitmap || !bitmap->data || !io || !handle) { return FALSE; } BOOL success = TRUE; // retrieve the plugin list to find the node belonging to this plugin PluginList *list = FreeImage_GetPluginList(); if (list) { PluginNode *node = list->FindNodeFromFIF(fif); if(node) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); // dst data void *data = FreeImage_Open(node, io, handle, FALSE); // src data void *data_read = NULL; if(header->handle) { // open src header->io->seek_proc(header->handle, 0, SEEK_SET); data_read = FreeImage_Open(header->node, header->io, header->handle, TRUE); } // write all the pages to the file using handle and io int count = 0; for (BlockListIterator i = header->m_blocks.begin(); i != header->m_blocks.end(); i++) { if (success) { switch((*i)->m_type) { case BLOCK_CONTINUEUS: { BlockContinueus *block = (BlockContinueus *)(*i); for (int j = block->m_start; j <= block->m_end; j++) { // load the original source data FIBITMAP *dib = header->node->m_plugin->load_proc(header->io, header->handle, j, header->load_flags, data_read); // save the data success = node->m_plugin->save_proc(io, dib, handle, count, flags, data); count++; FreeImage_Unload(dib); } break; } case BLOCK_REFERENCE: { BlockReference *ref = (BlockReference *)(*i); // read the compressed data BYTE *compressed_data = (BYTE*)malloc(ref->m_size * sizeof(BYTE)); header->m_cachefile->readFile((BYTE *)compressed_data, ref->m_reference, ref->m_size); // uncompress the data FIMEMORY *hmem = FreeImage_OpenMemory(compressed_data, ref->m_size); FIBITMAP *dib = FreeImage_LoadFromMemory(header->cache_fif, hmem, 0); FreeImage_CloseMemory(hmem); // get rid of the buffer free(compressed_data); // save the data success = node->m_plugin->save_proc(io, dib, handle, count, flags, data); count++; // unload the dib FreeImage_Unload(dib); break; } } } else { break; } } // close the files FreeImage_Close(header->node, header->io, header->handle, data_read); FreeImage_Close(node, io, handle, data); return success; } } return FALSE; } BOOL DLL_CALLCONV FreeImage_CloseMultiBitmap(FIMULTIBITMAP *bitmap, int flags) { if (bitmap) { BOOL success = TRUE; if (bitmap->data) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); // saves changes only of images loaded directly from a file if (header->changed && header->m_filename) { try { // open a temp file std::string spool_name; ReplaceExtension(spool_name, header->m_filename, "fispool"); // open the spool file and the source file FILE *f = fopen(spool_name.c_str(), "w+b"); // saves changes if (f == NULL) { FreeImage_OutputMessageProc(header->fif, "Failed to open %s, %s", spool_name.c_str(), strerror(errno)); success = FALSE; } else { success = FreeImage_SaveMultiBitmapToHandle(header->fif, bitmap, header->io, (fi_handle)f, flags); // close the files if (fclose(f) != 0) { success = FALSE; FreeImage_OutputMessageProc(header->fif, "Failed to close %s, %s", spool_name.c_str(), strerror(errno)); } } if (header->handle) { fclose((FILE *)header->handle); } // applies changes to the destination file if (success) { remove(header->m_filename); success = (rename(spool_name.c_str(), header->m_filename) == 0) ? TRUE:FALSE; if(!success) { FreeImage_OutputMessageProc(header->fif, "Failed to rename %s to %s", spool_name.c_str(), header->m_filename); } } else { remove(spool_name.c_str()); } } catch (std::bad_alloc &) { success = FALSE; } } else { if (header->handle && header->m_filename) { fclose((FILE *)header->handle); } } // clear the blocks list for (BlockListIterator i = header->m_blocks.begin(); i != header->m_blocks.end(); ++i) { delete *i; } // flush and dispose the cache if (header->m_cachefile) { header->m_cachefile->close(); delete header->m_cachefile; } // delete the last open bitmaps while (!header->locked_pages.empty()) { FreeImage_Unload(header->locked_pages.begin()->first); header->locked_pages.erase(header->locked_pages.begin()->first); } // get rid of the IO structure delete header->io; // delete the filename if(header->m_filename) { delete[] header->m_filename; } // delete the FIMULTIBITMAPHEADER delete header; } delete bitmap; return success; } return FALSE; } int DLL_CALLCONV FreeImage_GetPageCount(FIMULTIBITMAP *bitmap) { if (bitmap) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); if (header->page_count == -1) { header->page_count = 0; for (BlockListIterator i = header->m_blocks.begin(); i != header->m_blocks.end(); ++i) { switch((*i)->m_type) { case BLOCK_CONTINUEUS : header->page_count += ((BlockContinueus *)(*i))->m_end - ((BlockContinueus *)(*i))->m_start + 1; break; case BLOCK_REFERENCE : header->page_count++; break; } } } return header->page_count; } return 0; } static BlockReference* FreeImage_SavePageToBlock(MULTIBITMAPHEADER *header, FIBITMAP *data) { if (header->read_only || !header->locked_pages.empty()) return NULL; DWORD compressed_size = 0; BYTE *compressed_data = NULL; // compress the bitmap data // open a memory handle FIMEMORY *hmem = FreeImage_OpenMemory(); if(hmem==NULL) return NULL; // save the file to memory if(!FreeImage_SaveToMemory(header->cache_fif, data, hmem, 0)) { FreeImage_CloseMemory(hmem); return NULL; } // get the buffer from the memory stream if(!FreeImage_AcquireMemory(hmem, &compressed_data, &compressed_size)) { FreeImage_CloseMemory(hmem); return NULL; } // write the compressed data to the cache int ref = header->m_cachefile->writeFile(compressed_data, compressed_size); // get rid of the compressed data FreeImage_CloseMemory(hmem); return new(std::nothrow) BlockReference(ref, compressed_size); } void DLL_CALLCONV FreeImage_AppendPage(FIMULTIBITMAP *bitmap, FIBITMAP *data) { if (!bitmap || !data) return; MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); BlockReference *block = FreeImage_SavePageToBlock(header, data); if(block==NULL) return; // add the block header->m_blocks.push_back((BlockTypeS *)block); header->changed = TRUE; header->page_count = -1; } void DLL_CALLCONV FreeImage_InsertPage(FIMULTIBITMAP *bitmap, int page, FIBITMAP *data) { if (!bitmap || !data) return; if (page >= FreeImage_GetPageCount(bitmap)) return; MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); BlockReference *block = FreeImage_SavePageToBlock(header, data); if(block==NULL) return; // add a block if (page > 0) { BlockListIterator block_source = FreeImage_FindBlock(bitmap, page); header->m_blocks.insert(block_source, (BlockTypeS *)block); } else { header->m_blocks.push_front((BlockTypeS *)block); } header->changed = TRUE; header->page_count = -1; } void DLL_CALLCONV FreeImage_DeletePage(FIMULTIBITMAP *bitmap, int page) { if (bitmap) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); if ((!header->read_only) && (header->locked_pages.empty())) { if (FreeImage_GetPageCount(bitmap) > 1) { BlockListIterator i = FreeImage_FindBlock(bitmap, page); if (i != header->m_blocks.end()) { switch((*i)->m_type) { case BLOCK_CONTINUEUS : delete *i; header->m_blocks.erase(i); break; case BLOCK_REFERENCE : header->m_cachefile->deleteFile(((BlockReference *)(*i))->m_reference); delete *i; header->m_blocks.erase(i); break; } header->changed = TRUE; header->page_count = -1; } } } } } FIBITMAP * DLL_CALLCONV FreeImage_LockPage(FIMULTIBITMAP *bitmap, int page) { if (bitmap) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); // only lock if the page wasn't locked before... for (std::map::iterator i = header->locked_pages.begin(); i != header->locked_pages.end(); ++i) { if (i->second == page) { return NULL; } } // open the bitmap header->io->seek_proc(header->handle, 0, SEEK_SET); void *data = FreeImage_Open(header->node, header->io, header->handle, TRUE); // load the bitmap data if (data != NULL) { FIBITMAP *dib = (header->node->m_plugin->load_proc != NULL) ? header->node->m_plugin->load_proc(header->io, header->handle, page, header->load_flags, data) : NULL; // close the file FreeImage_Close(header->node, header->io, header->handle, data); // if there was still another bitmap open, get rid of it if (dib) { header->locked_pages[dib] = page; return dib; } return NULL; } } return NULL; } void DLL_CALLCONV FreeImage_UnlockPage(FIMULTIBITMAP *bitmap, FIBITMAP *page, BOOL changed) { if ((bitmap) && (page)) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); // find out if the page we try to unlock is actually locked... if (header->locked_pages.find(page) != header->locked_pages.end()) { // store the bitmap compressed in the cache for later writing if (changed && !header->read_only) { header->changed = TRUE; // cut loose the block from the rest BlockListIterator i = FreeImage_FindBlock(bitmap, header->locked_pages[page]); // compress the data DWORD compressed_size = 0; BYTE *compressed_data = NULL; // open a memory handle FIMEMORY *hmem = FreeImage_OpenMemory(); // save the page to memory FreeImage_SaveToMemory(header->cache_fif, page, hmem, 0); // get the buffer from the memory stream FreeImage_AcquireMemory(hmem, &compressed_data, &compressed_size); // write the data to the cache switch ((*i)->m_type) { case BLOCK_CONTINUEUS : { int iPage = header->m_cachefile->writeFile(compressed_data, compressed_size); delete (*i); *i = (BlockTypeS *)new BlockReference(iPage, compressed_size); break; } case BLOCK_REFERENCE : { BlockReference *reference = (BlockReference *)(*i); header->m_cachefile->deleteFile(reference->m_reference); delete (*i); int iPage = header->m_cachefile->writeFile(compressed_data, compressed_size); *i = (BlockTypeS *)new BlockReference(iPage, compressed_size); break; } } // get rid of the compressed data FreeImage_CloseMemory(hmem); } // reset the locked page so that another page can be locked FreeImage_Unload(page); header->locked_pages.erase(page); } } } BOOL DLL_CALLCONV FreeImage_MovePage(FIMULTIBITMAP *bitmap, int target, int source) { if (bitmap) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); if ((!header->read_only) && (header->locked_pages.empty())) { if ((target != source) && ((target >= 0) && (target < FreeImage_GetPageCount(bitmap))) && ((source >= 0) && (source < FreeImage_GetPageCount(bitmap)))) { BlockListIterator block_source = FreeImage_FindBlock(bitmap, target); BlockListIterator block_target = FreeImage_FindBlock(bitmap, source); header->m_blocks.insert(block_target, *block_source); header->m_blocks.erase(block_source); header->changed = TRUE; return TRUE; } } } return FALSE; } BOOL DLL_CALLCONV FreeImage_GetLockedPageNumbers(FIMULTIBITMAP *bitmap, int *pages, int *count) { if ((bitmap) && (count)) { MULTIBITMAPHEADER *header = FreeImage_GetMultiBitmapHeader(bitmap); if ((pages == NULL) || (*count == 0)) { *count = (int)header->locked_pages.size(); } else { int c = 0; for (std::map::iterator i = header->locked_pages.begin(); i != header->locked_pages.end(); ++i) { pages[c] = i->second; c++; if (c == *count) break; } } return TRUE; } return FALSE; } // ===================================================================== // Memory IO Multipage functions // ===================================================================== FIMULTIBITMAP * DLL_CALLCONV FreeImage_LoadMultiBitmapFromMemory(FREE_IMAGE_FORMAT fif, FIMEMORY *stream, int flags) { BOOL read_only = FALSE; // modifications (if any) will be stored into the memory cache // retrieve the plugin list to find the node belonging to this plugin PluginList *list = FreeImage_GetPluginList(); if (list) { PluginNode *node = list->FindNodeFromFIF(fif); if (node) { FreeImageIO *io = new(std::nothrow) FreeImageIO; if (io) { SetMemoryIO(io); FIMULTIBITMAP *bitmap = new(std::nothrow) FIMULTIBITMAP; if (bitmap) { MULTIBITMAPHEADER *header = new(std::nothrow) MULTIBITMAPHEADER; if (header) { header->m_filename = NULL; header->node = node; header->fif = fif; header->io = io; header->handle = (fi_handle)stream; header->changed = FALSE; header->read_only = read_only; header->m_cachefile = NULL; header->cache_fif = fif; header->load_flags = flags; // store the MULTIBITMAPHEADER in the surrounding FIMULTIBITMAP structure bitmap->data = header; // cache the page count header->page_count = FreeImage_InternalGetPageCount(bitmap); // allocate a continueus block to describe the bitmap header->m_blocks.push_back((BlockTypeS *)new BlockContinueus(0, header->page_count - 1)); if (!read_only) { // set up the cache CacheFile *cache_file = new(std::nothrow) CacheFile("", TRUE); if (cache_file && cache_file->open()) { header->m_cachefile = cache_file; } } return bitmap; } delete bitmap; } delete io; } } } return NULL; } BOOL DLL_CALLCONV FreeImage_SaveMultiBitmapToMemory(FREE_IMAGE_FORMAT fif, FIMULTIBITMAP *bitmap, FIMEMORY *stream, int flags) { if (stream && stream->data) { FreeImageIO io; SetMemoryIO(&io); return FreeImage_SaveMultiBitmapToHandle(fif, bitmap, &io, (fi_handle)stream, flags); } return FALSE; }