FreeImageStack::FreeImageStack(const std::string & rFileName, unsigned int nWidth, unsigned int nHeight): sFileName_(rFileName) , pImageStack_(0) , nWidth_(nWidth) , nHeight_(nHeight) , pBitmap_32f_(0) , nMaxXY_(0) , nMaxOffset_(0) { // open the bitmap pImageStack_ = FreeImage_OpenMultiBitmap(FIF_TIFF, (sFileName_ + "3D.tif").c_str(), TRUE, // create new FALSE, // open read-write FALSE, // keep all slices in memory TIFF_DEFAULT); NPP_ASSERT_NOT_NULL(pImageStack_); pBitmap_32f_ = FreeImage_AllocateT(FIT_FLOAT, nWidth_, nHeight_, 32 /* bits per pixel */); NPP_ASSERT_NOT_NULL(pBitmap_32f_); nMaxXY_ = std::min(nWidth_, nHeight_); nMaxOffset_ = static_cast<unsigned int>(sqrt(2 * static_cast<double>(nMaxXY_-1) * static_cast<double>(nMaxXY_-1))); aTimeStepAverages_.reserve(512); }
FIMULTIBITMAP* LoadAnim(const char* filename) { FreeImage_SetOutputMessage(FreeImageErrorHandler); FIMULTIBITMAP* ret(NULL); // try animated GIF { someError=false; ret = FreeImage_OpenMultiBitmap(FIF_GIF, filename, FALSE, TRUE); if (ret && !someError) { fprintf(stderr, "Loaded animated GIF..\n"); return ret; } } // try AVI { ret = ReadFromAvi(filename); if (ret) { fprintf(stderr, "Loaded AVI..\n"); return ret; } } return NULL; }
FreeImageStack::FreeImageStack(const std::string & rFileName): sFileName_(rFileName) , pImageStack_(0) , nWidth_(0) , nHeight_(0) , pBitmap_32f_(0) , nMaxXY_(0) , nMaxOffset_(0) { // open the bitmap pImageStack_ = FreeImage_OpenMultiBitmap(FIF_TIFF, (sFileName_ + ".tif").c_str(), FALSE, // create new TRUE, // open read-only FALSE, // keep all slices in memory TIFF_DEFAULT); NPP_ASSERT_NOT_NULL(pImageStack_); NPP_ASSERT_NOT_NULL(slices()); FIBITMAP * pBitmap = FreeImage_LockPage(pImageStack_, 0); // store away the size of the first image // this information is later used to insure that all slices // accessed are of the same size. if they are not an exception // is thrown when such a deviating slice is being accessed nWidth_ = FreeImage_GetWidth(pBitmap); nHeight_ = FreeImage_GetHeight(pBitmap); NPP_ASSERT(FreeImage_GetColorType(pBitmap) == FIC_MINISBLACK); NPP_ASSERT(FreeImage_GetBPP(pBitmap) == 8); FreeImage_UnlockPage(pImageStack_, pBitmap, FALSE); }
void ofxGifEncoder::doSave() { // create a multipage bitmap FIMULTIBITMAP *multi = FreeImage_OpenMultiBitmap(FIF_GIF, ofToDataPath(fileName).c_str(), TRUE, FALSE); for(int i = 0; i < frames.size(); i++ ) { ofxGifFrame * currentFrame = frames[i]; processFrame(currentFrame, multi); } FreeImage_CloseMultiBitmap(multi); }
BOOL fipMultiPage::open(const char* lpszPathName, BOOL create_new, BOOL read_only, int flags) { // try to guess the file format from the filename FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(lpszPathName); // check for supported file types if((fif == FIF_UNKNOWN) || (fif != FIF_TIFF) && (fif != FIF_ICO) && (fif != FIF_GIF)) return FALSE; // open the stream _mpage = FreeImage_OpenMultiBitmap(fif, lpszPathName, create_new, read_only, _bMemoryCache, flags); return (NULL != _mpage ) ? TRUE : FALSE; }
void ofxGifEncoder::doSave() { // create a multipage bitmap FIMULTIBITMAP *multi = FreeImage_OpenMultiBitmap(FIF_GIF, ofToDataPath(fileName).c_str(), TRUE, FALSE); FIBITMAP * bmp = NULL; for(int i = 0; i < frames.size(); i++ ) { // we need to swap the channels if we're on little endian (see ofImage::swapRgb); #ifdef TARGET_LITTLE_ENDIAN swapRgb(frames[i]); #endif // get the pixel data bmp = FreeImage_ConvertFromRawBits( frames[i]->pixels, frames[i]->width, frames[i]->height, frames[i]->width*(frames[i]->bitsPerPixel/8), frames[i]->bitsPerPixel, 0, 0, 0, true // in of006 this (topdown) had to be false. ); #ifdef TARGET_LITTLE_ENDIAN swapRgb(frames[i]); #endif DWORD frameDuration = (DWORD) (frames[i]->duration * 1000.f); bmp = FreeImage_ColorQuantizeEx(bmp, FIQ_NNQUANT, nColors); // dithering :) if(ditherMode > OFX_GIF_DITHER_NONE) bmp = FreeImage_Dither(bmp, (FREE_IMAGE_DITHER)ditherMode); // clear any animation metadata used by this dib as we’ll adding our own ones FreeImage_SetMetadata(FIMD_ANIMATION, bmp, NULL, NULL); // add animation tags to dib[i] FITAG *tag = FreeImage_CreateTag(); if(tag) { FreeImage_SetTagKey(tag, "FrameTime"); FreeImage_SetTagType(tag, FIDT_LONG); FreeImage_SetTagCount(tag, 1); FreeImage_SetTagLength(tag, 4); FreeImage_SetTagValue(tag, &frameDuration); FreeImage_SetMetadata(FIMD_ANIMATION, bmp, FreeImage_GetTagKey(tag), tag); FreeImage_DeleteTag(tag); } FreeImage_AppendPage(multi, bmp); } FreeImage_Unload(bmp); FreeImage_CloseMultiBitmap(multi); }
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; }
bool CloneMultiPage(FREE_IMAGE_FORMAT fif, char *input, char *output, int output_flag) { BOOL bMemoryCache = TRUE; // Open src file (read-only, use memory cache) FIMULTIBITMAP *src = FreeImage_OpenMultiBitmap(fif, input, FALSE, TRUE, bMemoryCache); if(src) { // Open dst file (creation, use memory cache) FIMULTIBITMAP *dst = FreeImage_OpenMultiBitmap(fif, output, TRUE, FALSE, bMemoryCache); // Get src page count int count = FreeImage_GetPageCount(src); // Clone src to dst for(int page = 0; page < count; page++) { // Load the bitmap at position 'page' FIBITMAP *dib = FreeImage_LockPage(src, page); if(dib) { // add a new bitmap to dst FreeImage_AppendPage(dst, dib); // Unload the bitmap (do not apply any change to src) FreeImage_UnlockPage(src, dib, FALSE); } } // Close src FreeImage_CloseMultiBitmap(src, 0); // Save and close dst FreeImage_CloseMultiBitmap(dst, output_flag); return true; } return false; }
// return a bool if succesful? bool ofxGifDecoder::decode(string fileName) { reset(); int width, height, bpp; fileName = ofToDataPath(fileName); bool bDecoded = false; FIMULTIBITMAP* multiBmp = NULL; FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; fif = FreeImage_GetFileType(fileName.c_str(), 0); if(fif != FIF_GIF) { ofLog(OF_LOG_WARNING, "ofxGifDecoder::decode. this is not a gif file. not processing"); return bDecoded; } multiBmp = FreeImage_OpenMultiBitmap(fif, fileName.c_str(), false, false,true, GIF_LOAD256); if (multiBmp){ // num frames int nPages = FreeImage_GetPageCount(multiBmp); // here we process the first frame for (int i = 0; i < nPages; i++) { FIBITMAP * dib = FreeImage_LockPage(multiBmp, i); if(dib) { if (i == 0) { createGifFile(dib, nPages); bDecoded = true; // we have at least 1 frame } processFrame(dib, i); FreeImage_UnlockPage(multiBmp, dib, false); } else { ofLog(OF_LOG_WARNING, "ofxGifDecoder::decode. problem locking page"); } } FreeImage_CloseMultiBitmap(multiBmp, 0); }else { ofLog(OF_LOG_WARNING, "ofxGifDecoder::decode. there was an error processing."); } return bDecoded; }
static FIMULTIBITMAP* ReadFromAvi(const char* filename) { int err=0; AVIFileInit(); PAVISTREAM pavi; // Handle To An Open Stream if( AVIStreamOpenFromFile(&pavi, filename, streamtypeVIDEO, 0, OF_READ, NULL) != 0) { AVIFileExit(); return NULL; } AVISTREAMINFO psi; // Pointer To A Structure Containing Stream Info AVIStreamInfo(pavi, &psi, sizeof(psi)); // Reads Information About The Stream Into psi int width = psi.rcFrame.right-psi.rcFrame.left; // Width Is Right Side Of Frame Minus Left int height = psi.rcFrame.bottom-psi.rcFrame.top; // Height Is Bottom Of Frame Minus Top int frameCount = AVIStreamLength(pavi); // The Last Frame Of The Stream double mpf = AVIStreamSampleToTime(pavi, frameCount) / (double)frameCount; // Calculate Rough Milliseconds Per Frame PGETFRAME pgf = AVIStreamGetFrameOpen(pavi, NULL); // Create The PGETFRAME Using Our Request Mode if (pgf==NULL) { // An Error Occurred Opening The Frame error("Failed To Open frame from AVI"); } //HDC hdc = GetDC(0); HDRAWDIB hdd = DrawDibOpen(); // Handle For Our Dib BITMAPINFOHEADER bmih; // Header Information For DrawDibDraw Decoding bmih.biSize = sizeof (BITMAPINFOHEADER); // Size Of The BitmapInfoHeader bmih.biPlanes = 1; // Bitplanes bmih.biBitCount = 24; // Bits Format We Want (24 Bit, 3 Bytes) bmih.biWidth = width; // Width We Want (256 Pixels) bmih.biHeight = height; // Height We Want (256 Pixels) bmih.biCompression = BI_RGB; // Requested Mode = RGB char *data; // Pointer To Texture Data HBITMAP hBitmap = CreateDIBSection(hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL); SelectObject(hdc, hBitmap); // Select hBitmap Into Our Device Context (hdc) // create a new freeimage anim someError=false; FIMULTIBITMAP* ret = FreeImage_OpenMultiBitmap(FIF_TIFF, "temp.tiff", TRUE, FALSE); if (!ret || someError) { error("Couldnt create multibitmap"); } for (int frame=0; frame<frameCount; frame++) { fprintf(stderr, "Loading frame %i\n", frame); // Grab Data From The AVI Stream LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame); // Pointer To Data Returned By AVIStreamGetFrame // (Skip The Header Info To Get To The Data) char* pdata = (char *)lpbi + lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD); // Convert Data To Requested Bitmap Format DrawDibDraw(hdd, hdc, 0, 0, width, height, lpbi, pdata, 0, 0, width, height, 0); // copy into the freeimage thing FIBITMAP* fiBitmap = FreeImage_ConvertFromRawBits((BYTE*)data, width, height, width*3, 24, 0xFF0000, 0x00FF00, 0x0000FF); /* BYTE* src = (BYTE*)data; for (int y=0; y<height; y++) { BYTE* dst = FreeImage_GetScanLine(fiBitmap, y); for (int x=0; x<width; x++) { //src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; } } */ FIBITMAP* grayBitmap = FreeImage_ConvertToGreyscale(fiBitmap); FreeImage_Unload(fiBitmap); FreeImage_AppendPage(ret, grayBitmap); } FreeImage_CloseMultiBitmap(ret); ret = FreeImage_OpenMultiBitmap(FIF_TIFF, "temp.tiff", FALSE, TRUE); DeleteObject(hBitmap); // Delete The Device Dependant Bitmap Object DrawDibClose(hdd); // Closes The DrawDib Device Context AVIStreamGetFrameClose(pgf); // Deallocates The GetFrame Resources AVIStreamRelease(pavi); // Release The Stream AVIFileExit(); // Release The File return ret; }
static void *bg_new_img(void *data) { /* so we can poll for it */ block_usr1_signal(); struct imv_loader *ldr = data; char path[PATH_MAX] = "-"; pthread_mutex_lock(&ldr->lock); int from_stdin = !strncmp(path, ldr->path, 2); if(!from_stdin) { (void)snprintf(path, PATH_MAX, "%s", ldr->path); } pthread_mutex_unlock(&ldr->lock); FREE_IMAGE_FORMAT fmt; if (from_stdin) { pthread_mutex_lock(&ldr->lock); ldr->fi_buffer = FreeImage_OpenMemory(ldr->buffer, ldr->buffer_size); fmt = FreeImage_GetFileTypeFromMemory(ldr->fi_buffer, 0); pthread_mutex_unlock(&ldr->lock); } else { fmt = FreeImage_GetFileType(path, 0); } if(fmt == FIF_UNKNOWN) { if (from_stdin) { pthread_mutex_lock(&ldr->lock); FreeImage_CloseMemory(ldr->fi_buffer); pthread_mutex_unlock(&ldr->lock); } error_occurred(ldr); return NULL; } int num_frames = 1; FIMULTIBITMAP *mbmp = NULL; FIBITMAP *bmp = NULL; int width, height; int raw_frame_time = 100; /* default to 100 */ if(fmt == FIF_GIF) { if(from_stdin) { pthread_mutex_lock(&ldr->lock); mbmp = FreeImage_LoadMultiBitmapFromMemory(FIF_GIF, ldr->fi_buffer, GIF_LOAD256); pthread_mutex_unlock(&ldr->lock); } else { mbmp = FreeImage_OpenMultiBitmap(FIF_GIF, path, /* don't create file */ 0, /* read only */ 1, /* keep in memory */ 1, /* flags */ GIF_LOAD256); } if(!mbmp) { error_occurred(ldr); return NULL; } num_frames = FreeImage_GetPageCount(mbmp); FIBITMAP *frame = FreeImage_LockPage(mbmp, 0); width = FreeImage_GetWidth(frame); height = FreeImage_GetHeight(frame); bmp = FreeImage_ConvertTo32Bits(frame); /* get duration of first frame */ FITAG *tag = NULL; FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if(FreeImage_GetTagValue(tag)) { raw_frame_time = *(int*)FreeImage_GetTagValue(tag); } FreeImage_UnlockPage(mbmp, frame, 0); } else { /* Future TODO: If we load image line-by-line we could stop loading large * ones before wasting much more time/memory on them. */ int flags = (fmt == FIF_JPEG) ? JPEG_EXIFROTATE : 0; FIBITMAP *image; if(from_stdin) { pthread_mutex_lock(&ldr->lock); image = FreeImage_LoadFromMemory(fmt, ldr->fi_buffer, flags); pthread_mutex_unlock(&ldr->lock); } else { image = FreeImage_Load(fmt, path, flags); } if(!image) { error_occurred(ldr); pthread_mutex_lock(&ldr->lock); FreeImage_CloseMemory(ldr->fi_buffer); ldr->fi_buffer = NULL; pthread_mutex_unlock(&ldr->lock); return NULL; } /* Check for cancellation before we convert pixel format */ if(is_thread_cancelled()) { FreeImage_Unload(image); return NULL; } width = FreeImage_GetWidth(bmp); height = FreeImage_GetHeight(bmp); bmp = FreeImage_ConvertTo32Bits(image); FreeImage_Unload(image); } /* now update the loader */ pthread_mutex_lock(&ldr->lock); /* check for cancellation before finishing */ if(is_thread_cancelled()) { if(mbmp) { FreeImage_CloseMultiBitmap(mbmp, 0); } if(bmp) { FreeImage_Unload(bmp); } pthread_mutex_unlock(&ldr->lock); return NULL; } if(ldr->mbmp) { FreeImage_CloseMultiBitmap(ldr->mbmp, 0); } if(ldr->bmp) { FreeImage_Unload(ldr->bmp); } ldr->mbmp = mbmp; ldr->bmp = bmp; if(ldr->out_bmp) { FreeImage_Unload(ldr->out_bmp); } ldr->out_bmp = FreeImage_Clone(bmp); ldr->out_is_new_image = 1; ldr->width = width; ldr->height = height; ldr->cur_frame = 0; ldr->next_frame = 1; ldr->num_frames = num_frames; ldr->frame_time = (double)raw_frame_time * 0.0001; pthread_mutex_unlock(&ldr->lock); return NULL; }
/* * Render a particular frame to a canvas (not gif specific). */ bool CAnimation::renderFileFrame(CCanvas *cnv, unsigned int frame) { extern STRING g_projectPath; cnv->ClearScreen(TRANSP_COLOR); // Wrap around. frame %= m_data.frameCount; if (m_data.filename.empty()) return false; const STRING file = resolve(g_projectPath + MISC_PATH + m_data.filename); FIMULTIBITMAP *mbmp = FreeImage_OpenMultiBitmap( FreeImage_GetFileType(getAsciiString(file).c_str(), 16), getAsciiString(file).c_str(), FALSE, TRUE, TRUE ); if (!mbmp) return false; CCanvas cnvImg; cnvImg.CreateBlank(NULL, m_data.pxWidth, m_data.pxHeight, TRUE); const int pageCount = FreeImage_GetPageCount(mbmp); if (frame < pageCount) { CONST HDC hdc = cnvImg.OpenDC(); FIBITMAP *bmp = FreeImage_LockPage(mbmp, frame); SetDIBitsToDevice( hdc, 0, 0, m_data.pxWidth, m_data.pxHeight, 0, 0, 0, FreeImage_GetHeight(bmp), FreeImage_GetBits(bmp), FreeImage_GetInfo(bmp), DIB_RGB_COLORS ); /* No need to stretch gif. StretchDIBits( hdc, 0, 0, m_data.pxWidth, m_data.pxHeight, 0, 0, FreeImage_GetWidth(bmp), FreeImage_GetHeight(bmp), FreeImage_GetBits(bmp), FreeImage_GetInfo(bmp), DIB_RGB_COLORS, SRCCOPY );*/ FreeImage_UnlockPage(mbmp, bmp, FALSE); cnvImg.CloseDC(hdc); } FreeImage_CloseMultiBitmap(mbmp, 0); // Apply ambient level. extern AMBIENT_LEVEL g_ambientLevel; if (g_ambientLevel.color) { CCanvas cnvAl; cnvAl.CreateBlank(NULL, m_data.pxWidth, m_data.pxHeight, TRUE); cnvAl.ClearScreen(g_ambientLevel.color); cnvAl.BltAdditivePart(cnvImg.GetDXSurface(), 0, 0, 0, 0, m_data.pxWidth, m_data.pxHeight, g_ambientLevel.sgn, -1, m_data.transpColors[frame]); } cnvImg.BltTransparent(cnv, 0, 0, m_data.transpColors[frame]); return true; }
/* * Render all frames of a file (not gif specific). */ bool CAnimation::renderFileFrame(CCanvas *, unsigned int) { extern STRING g_projectPath; if (m_data.filename.empty()) return false; const STRING file = resolve(g_projectPath + MISC_PATH + m_data.filename); FIMULTIBITMAP *mbmp = FreeImage_OpenMultiBitmap( FreeImage_GetFileType(getAsciiString(file).c_str(), 16), getAsciiString(file).c_str(), FALSE, TRUE, TRUE ); if (!mbmp) return false; // Create ambient level canvas. extern AMBIENT_LEVEL g_ambientLevel; CCanvas cnvAl; if (g_ambientLevel.color) { cnvAl.CreateBlank(NULL, m_data.pxWidth, m_data.pxHeight, TRUE); cnvAl.ClearScreen(g_ambientLevel.color); } // Intermediate canvas. CCanvas cnv; cnv.CreateBlank(NULL, m_data.pxWidth, m_data.pxHeight, TRUE); freeCanvases(); m_canvases.clear(); for (int i = 0; i != m_data.frameCount; ++i) { CONST HDC hdc = cnv.OpenDC(); FIBITMAP *bmp = FreeImage_LockPage(mbmp, i); SetDIBitsToDevice( hdc, 0, 0, m_data.pxWidth, m_data.pxHeight, 0, 0, 0, FreeImage_GetHeight(bmp), FreeImage_GetBits(bmp), FreeImage_GetInfo(bmp), DIB_RGB_COLORS ); /* No need to stretch gif. StretchDIBits( hdc, 0, 0, 0, 0, FreeImage_GetWidth(bmp), FreeImage_GetHeight(bmp), FreeImage_GetBits(bmp), FreeImage_GetInfo(bmp), DIB_RGB_COLORS, SRCCOPY );*/ FreeImage_UnlockPage(mbmp, bmp, FALSE); cnv.CloseDC(hdc); // Apply ambient level. if (g_ambientLevel.color) { cnvAl.BltAdditivePart(cnv.GetDXSurface(), 0, 0, 0, 0, m_data.pxWidth, m_data.pxHeight, g_ambientLevel.sgn, -1, m_data.transpColors[i]); } // Blt to the member canvas. CCanvas *pCnv = new CCanvas(); pCnv->CreateBlank(NULL, m_data.pxWidth, m_data.pxHeight, TRUE); pCnv->ClearScreen(TRANSP_COLOR); cnv.BltTransparent(pCnv, 0, 0, m_data.transpColors[i]); m_canvases.push_back(pCnv); } FreeImage_CloseMultiBitmap(mbmp, 0); return true; }
FreeImageGifData::FreeImageGifData(const std::string& filename, bool newInit /*= true*/) : m_filename(filename), m_gifHandle(nullptr) { m_gifHandle = FreeImage_OpenMultiBitmap(FIF_GIF, filename.c_str(), newInit, false); }
//-------------------------------------------------------------- void ofxGifEncoder::save (vector <ofxGifFrame *> frames, string fileName, int nColors) { if (nColors < 2 || nColors > 256) { ofLog(OF_LOG_WARNING, "nColors must be between 2 and 256. your gif won't be saved"); return; } // create a multipage bitmap FIMULTIBITMAP *multi = FreeImage_OpenMultiBitmap(FIF_GIF, ofToDataPath(fileName).c_str(), TRUE, FALSE); FIBITMAP * bmp = NULL; for(int i = 0; i < frames.size(); i++ ) { // we need to swap the channels if we're on little endian (see ofImage::swapRgb); #ifdef TARGET_LITTLE_ENDIAN swapRgb(frames[i]); #endif // get the pixel data bmp = FreeImage_ConvertFromRawBits( frames[i]->pixels, frames[i]->width, frames[i]->height, frames[i]->width*(frames[i]->bitsPerPixel/8), frames[i]->bitsPerPixel, 0, 0, 0, true // in of006 this (topdown) had to be false. ); #ifdef TARGET_LITTLE_ENDIAN swapRgb(frames[i]); #endif DWORD frameDuration = (DWORD) frames[i]->duration * 1000.f; // bmp = FreeImage_ColorQuantize(bmp, FIQ_NNQUANT); // if we want to set a reduced color palette (2 to 256); bmp = FreeImage_ColorQuantizeEx(bmp, FIQ_NNQUANT, nColors); // dithering :) // you can set a different dither pattern for each frame // bmp = FreeImage_Dither(bmp, (FREE_IMAGE_DITHER)((i+1)%6)); //bmp = FreeImage_Dither(bmp, FID_BAYER8x8); // clear any animation metadata used by this dib as we’ll adding our own ones FreeImage_SetMetadata(FIMD_ANIMATION, bmp, NULL, NULL); // add animation tags to dib[i] FITAG *tag = FreeImage_CreateTag(); if(tag) { FreeImage_SetTagKey(tag, "FrameTime"); FreeImage_SetTagType(tag, FIDT_LONG); FreeImage_SetTagCount(tag, 1); FreeImage_SetTagLength(tag, 4); FreeImage_SetTagValue(tag, &frameDuration); FreeImage_SetMetadata(FIMD_ANIMATION, bmp, FreeImage_GetTagKey(tag), tag); FreeImage_DeleteTag(tag); } FreeImage_AppendPage(multi, bmp); } FreeImage_Unload(bmp); FreeImage_CloseMultiBitmap(multi); }