// This must be called before any frame-saving is attempted. void CAnimationExporter::CreateGif() { if(!g_canvas || !g_canvas->model || !g_canvas->model->animManager) { wxMessageBox(_T("Unable to create animated GIF!"), _T("Error") ); wxLogMessage(_T( "Error: Unable to created animated GIF. A required objects pointer was null!") ); Show(false); return ; } CxImage **gifImages = NULL; // Our pointer array of images // Reset the state of our GUI objects btnStart->Enable(false); btnCancel->Enable(false); cbGrey->Enable(false); cbTrans->Enable(false); cbDither->Enable(false); cbShrink->Enable(false); txtFrames->Enable(false); txtSizeX->Enable(false); txtSizeY->Enable(false); txtDelay->Enable(false); // Pause our rendering to screen so we can focus on making the animated image g_videoSetting.render = false; m_fAnimSpeed = g_canvas->model->animManager->GetSpeed(); // Save the old animation speed g_canvas->model->animManager->SetSpeed(1.0f); // Set it to the normal speed. m_iTotalAnimFrames = g_canvas->model->animManager->GetFrameCount(); wxString(txtFrames->GetValue() ).ToLong( (long*) &m_iTotalFrames); wxString(txtDelay->GetValue() ).ToLong( (long*) &m_iDelay); // will crash program - prevent this from happening if(m_iTotalFrames > m_iTotalAnimFrames) { wxMessageBox(_T( "Impossible to make a gif with more frames than the model animation.\nClosing gif exporter."), _T("Error") ); wxLogMessage(_T( "Error: Unable to make a gif with more frames than the model animation.") ); this->Show(false); return ; } if(m_iDelay < 1) { m_iDelay = 1; } if(m_iDelay > 100) { m_iDelay = 100; } m_iTimeStep = int(m_iTotalAnimFrames / m_iTotalFrames); // Total number of frames in the animation / total frames going into our exported animation image if(m_bShrink) { wxString(txtSizeX->GetValue() ).ToLong( (long*) &m_iNewWidth); wxString(txtSizeY->GetValue() ).ToLong( (long*) &m_iNewHeight); // Just a minor check, final image size can not be smaller than 32x32 pixels. if(m_iNewWidth < 32 || m_iNewHeight < 32) { m_iNewWidth = 32; m_iNewHeight = 32; } } // CREATE OUR RENDERTOTEXTURE OBJECT // ------------------------------------------- // if either are supported use our 'RenderTexture' object. if(g_videoSetting.supportPBO || g_videoSetting.supportVBO) { g_canvas->rt = new RenderTexture(); if(!g_canvas->rt) { wxLogMessage(_T("Error: RenderToTexture object is null!") ); this->Show(false); return ; } g_canvas->rt->Init( (HWND)g_canvas->GetHandle(), 0, 0, g_videoSetting.supportFBO) ; m_iWidth = g_canvas->rt->nWidth; m_iHeight = g_canvas->rt->nHeight; g_canvas->rt->BeginRender(); } else { glReadBuffer(GL_BACK); int screenSize[4]; glGetIntegerv(GL_VIEWPORT, screenSize); // get the width/height of the canvas m_iWidth = screenSize[2]; m_iHeight = screenSize[3]; return ; } // Stop our animation g_canvas->model->animManager->Pause(true); g_canvas->model->animManager->Stop(); g_canvas->model->animManager->AnimateParticles(); // Size of our buffer to hold the pixel data m_iSize = m_iWidth * m_iHeight * 4; // (width*height*bytesPerPixel) // Create one frame to make our optimal colour palette from. unsigned char *buffer = new unsigned char[m_iSize]; gifImages = new CxImage *[m_iTotalFrames]; for(unsigned int i = 0; i < m_iTotalFrames; i++) { lblCurFrame->SetLabel(wxString::Format(_T("Current Frame: %i"), i) ); this->Refresh(); this->Update(); CxImage *newImage = new CxImage(0); g_canvas->RenderToBuffer(); glReadPixels(0, 0, m_iWidth, m_iHeight, GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer); newImage->CreateFromArray(buffer, m_iWidth, m_iHeight, 32, (m_iWidth *4) , false); // not needed due to the code just below, which fixes the issue with particles //g_canvas->model->animManager->SetTimeDiff(m_iTimeStep); //g_canvas->model->animManager->Tick(m_iTimeStep); if(g_canvas->root) { g_canvas->root->tick( (float)m_iTimeStep); } if(g_canvas->sky) { g_canvas->sky->tick( (float)m_iTimeStep); } #ifdef _WIN32 if(m_bGreyscale) { newImage->GrayScale(); } #endif //_WIN32 if(m_bShrink && m_iNewWidth != m_iWidth && m_iNewHeight != m_iHeight) { newImage->Resample(m_iNewWidth, m_iNewHeight, 2); } // if (Optimise) { if(!m_pPal) { CQuantizer q(256, 8); q.ProcessImage( (HANDLE)newImage->GetDIB() ); m_pPal = (RGBQUAD*)calloc(256 *sizeof(RGBQUAD), 1); //This creates our gifs optimised global colour palette q.SetColorTable(m_pPal); } newImage->DecreaseBpp(8, m_bDiffuse, m_pPal, 256); newImage->SetCodecOption(2); // for LZW compression if(m_bTransparent) { newImage->SetTransIndex(newImage->GetPixelIndex(0, 0) ); } newImage->SetFrameDelay(m_iDelay); gifImages[i] = newImage; // All the memory that we allocate for newImage gets cleared at the end } wxDELETEA(buffer); if(g_videoSetting.supportPBO || g_videoSetting.supportVBO) { g_canvas->rt->EndRender(); // Clear RenderTexture object. g_canvas->rt->Shutdown(); wxDELETE(g_canvas->rt); } // CREATE THE ACTUAL MULTI-IMAGE GIF ANIMATION // ------------------------------------------------------ // Create the file and write all the data // Open/Create the file that were going to save to FILE *hFile = NULL; hFile = fopen(m_strFilename.fn_str(), "wb"); // Set gif options CxImageGIF multiImage; multiImage.SetComment("Exported from WoW Model Viewer"); if(m_bTransparent) { multiImage.SetDisposalMethod(2); } else { multiImage.SetDisposalMethod(0); } multiImage.SetFrameDelay(m_iDelay); multiImage.SetCodecOption(2); // LZW multiImage.SetLoops(0); // Set the animation to loop indefinately. // Create/Compose the animated gif multiImage.Encode(hFile, gifImages, m_iTotalFrames, false); // ALL DONE, START THE CLEAN UP // -------------------------------------------------------- // Close file fclose(hFile); // Free the memory used by all the images to create the GIF for(unsigned int i = 0; i < m_iTotalFrames; i++) { gifImages[i]->Destroy(); wxDELETE(gifImages[i]); } wxDELETEA(gifImages); // Free memory used by the colour palette if(m_pPal) { free(m_pPal); m_pPal = NULL; } wxLogMessage(_T("Info: GIF Animation successfully created.") ); g_canvas->model->animManager->SetSpeed(m_fAnimSpeed); // Return the animation speed back to whatever it was previously set as g_canvas->model->animManager->Play(); Show(false); g_videoSetting.render = true; g_canvas->InitView(); }
UINT CFrameGrabThread::GrabFrames(){ #define TIMEBETWEENFRAMES 50.0 // could be a param later, if needed for (int i = 0; i!= nFramesToGrab; i++) imgResults[i] = NULL; try{ HRESULT hr; CComPtr<IMediaDet> pDet; hr = pDet.CoCreateInstance(__uuidof(MediaDet)); if (!SUCCEEDED(hr)) return 0; // Convert the file name to a BSTR. CComBSTR bstrFilename(strFileName); hr = pDet->put_Filename(bstrFilename); long lStreams; bool bFound = false; hr = pDet->get_OutputStreams(&lStreams); for (long i = 0; i < lStreams; i++) { GUID major_type; hr = pDet->put_CurrentStream(i); hr = pDet->get_StreamType(&major_type); if (major_type == MEDIATYPE_Video) { bFound = true; break; } } if (!bFound) return 0; double dLength = 0; pDet->get_StreamLength(&dLength); if (dStartTime > dLength) dStartTime = 0; long width = 0, height = 0; AM_MEDIA_TYPE mt; hr = pDet->get_StreamMediaType(&mt); if (mt.formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat); width = pVih->bmiHeader.biWidth; height = pVih->bmiHeader.biHeight; // We want the absolute height, don't care about orientation. if (height < 0) height *= -1; } else { return 0; // Should not happen, in theory. } /*FreeMediaType(mt); = */ if (mt.cbFormat != 0){ CoTaskMemFree((PVOID)mt.pbFormat); mt.cbFormat = 0; mt.pbFormat = NULL; } if (mt.pUnk != NULL){ mt.pUnk->Release(); mt.pUnk = NULL; } /**/ long size; uint32 nFramesGrabbed; for (nFramesGrabbed = 0; nFramesGrabbed != nFramesToGrab; nFramesGrabbed++){ hr = pDet->GetBitmapBits(dStartTime + (nFramesGrabbed*TIMEBETWEENFRAMES), &size, NULL, width, height); if (SUCCEEDED(hr)) { // we could also directly create a Bitmap in memory, however this caused problems/failed with *some* movie files // when I tried it for the MMPreview, while this method works always - so I'll continue to use this one long nFullBufferLen = sizeof( BITMAPFILEHEADER ) + size; char* buffer = new char[nFullBufferLen]; BITMAPFILEHEADER bfh; memset( &bfh, 0, sizeof( bfh ) ); bfh.bfType = 'MB'; bfh.bfSize = nFullBufferLen; bfh.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER ); memcpy(buffer,&bfh,sizeof( bfh ) ); try { hr = pDet->GetBitmapBits(dStartTime+ (nFramesGrabbed*TIMEBETWEENFRAMES), NULL, buffer + sizeof( bfh ), width, height); } catch (...) { ASSERT(0); hr = E_FAIL; } if (SUCCEEDED(hr)) { // decode CxImage* imgResult = new CxImage(); imgResult->Decode((BYTE*)buffer, nFullBufferLen, CXIMAGE_FORMAT_BMP); delete[] buffer; if (!imgResult->IsValid()){ delete imgResult; break; } // resize if needed if (nMaxWidth > 0 && nMaxWidth < width){ float scale = (float)nMaxWidth / imgResult->GetWidth(); int nMaxHeigth = (int)(imgResult->GetHeight() * scale); imgResult->Resample(nMaxWidth, nMaxHeigth, 0); } // decrease bpp if needed if (bReduceColor){ RGBQUAD* ppal=(RGBQUAD*)malloc(256*sizeof(RGBQUAD)); if (ppal) { CQuantizer q(256,8); q.ProcessImage(imgResult->GetDIB()); q.SetColorTable(ppal); imgResult->DecreaseBpp(8, true, ppal); free(ppal); } } //CString TestName; //TestName.Format("G:\\testframe%i.png",nFramesGrabbed); //imgResult->Save(TestName,CXIMAGE_FORMAT_PNG); // done imgResults[nFramesGrabbed] = imgResult; } else{ delete[] buffer; break; } } } return nFramesGrabbed; } catch(...){ ASSERT(0); return 0; } }