Create a Video from Still Images - Windows C++

This tutorial shows how to use the LEADTOOLS Multimedia SDK to create a Windows C++ application that uses the ltmmConvert control and other LEADTOOLS objects to convert images to video.

Overview  
Summary This tutorial covers how to use the ltmmConvert object to create a video from multiple images in a Windows C++ application.
Completion Time 30 minutes
Visual Studio Project Download tutorial project (21 KB)
Platform Windows API C++ Application
IDE Visual Studio 2019
Development License Download LEADTOOLS
Try it in another language

Required Knowledge

Before working on the Create a Video from Still Images - Windows C++ tutorial, complete the Add References and Set a License tutorial.

Create the Project and Add the Multimedia Headers and LIB Files

Start with a copy of the 64-bit Windows API project created in the Add References and Set a License tutorial. If the project is not available, create it by following the steps in that tutorial.

In order to use the ltmmConvert Object, LEADTOOLS requires additional references. Add the required Multimedia library reference by opening the pre-compiled header file, either pch.h or stdafx.h depending on the Visual Studio version used, and add the following lines:

// Add LEADTOOLS Multimedia reference 
#include "C:\LEADTOOLS22\Include\ltmm.h" 
#include "C:\LEADTOOLS22\Include\ltmm_Errors.h" 
 
//x64 libs 
#pragma comment (lib, "C:\\LEADTOOLS22\\Lib\\CDLL\\x64\\Ltmmx.lib") 
#pragma comment (lib, "C:\\LEADTOOLS22\\Lib\\CDLL\\x64\\ltmmuuidx.lib") 

The code of this tutorial loads image files and performs some processing on them before converting them to video frames, so the following references are required as well:

#pragma comment (lib, "C:\\LEADTOOLS22\\Lib\\CDLL\\x64\\Ltfil_x.lib") // image loading library 
#pragma comment (lib, "C:\\LEADTOOLS22\\Lib\\CDLL\\x64\\Ltimgefx_x.lib") // image processing effects library 

Add the Code to Convert Still Images to Video

With the project created, the references added, and the license set, coding can begin.

The steps below are for Visual Studio 2019; they could be different for other versions of Visual Studio.

Go to the Solution Explorer and double-click the resources file (.rc). Expand the menu tab in the resources tree and double-click the menu resource to open it in the designer interface. In the empty item below the Exit item, click and type &Create Video From Images. Drag the new item above Exit. Ensure the item's ID is ID_FILE_CREATEVIDEOFROMIMAGES.

Go to the main CPP file of the project, which contains the WndProc() function for the main window. Navigate to the switch (wmId) statement that is below the WM_COMMAND case and add the new case shown below.

// In WndProc(), under "case WM_COMMAND:" 
switch (wmId) 
{ 
case ID_FILE_CREATEVIDEOFROMIMAGES: 
   // initialize COM 
   CoInitialize(NULL); 
   // Create the video 
   GenerateVideo(hWnd); 
   // Cleanup the objects used 
   if (pMediaType) 
      pMediaType->Release(); 
   pMediaType = NULL; 
   if (pSampleSource) 
      pSampleSource->Release(); 
   pSampleSource = NULL; 
   if (pConvert) 
      pConvert->Release(); 
   pConvert = NULL; 
   CoUninitialize(); 
   break; 
   // Keep rest of the code as is 

Add a new function GenerateVideo(HWND hWnd) that loads the images and creates an AVI file from them. Below is the code of the function and the variables and data types needed for it. This code can be added above the WndProc function:

typedef LONGLONG REFERENCE_TIME; 
 
typedef struct tagVIDEOINFOHEADER { 
   RECT                rcSource; 
   RECT                rcTarget; 
   DWORD               dwBitRate; 
   DWORD               dwBitErrorRate; 
   REFERENCE_TIME      AvgTimePerFrame; 
   BITMAPINFOHEADER    bmiHeader; 
} VIDEOINFOHEADER; 
 
IltmmConvert* pConvert = NULL; 
IltmmSampleSource* pSampleSource = NULL; 
IltmmMediaTypeDisp* pMediaType = NULL; 
void GenerateVideo(HWND hWnd) 
{ 
   TCHAR szFolderIn[1024] = TEXT(""); // Input files location 
   // Choose input files folder 
   if (SUCCESS != GetSourceFolder(hWnd, szFolderIn, ARRAYSIZE(szFolderIn))) 
      return; 
   TCHAR szFolderSearch[1024] = TEXT(""); // Input files search location and pattern 
   _tcscpy_s(szFolderSearch, ARRAYSIZE(szFolderSearch), szFolderIn); 
   _tcscat_s(szFolderSearch, ARRAYSIZE(szFolderSearch), TEXT("\\*.jpg")); 
   WIN32_FIND_DATAW FindFileData = { 0 }; 
   HANDLE hFind = FindFirstFile(szFolderSearch, &FindFileData); 
   if (INVALID_HANDLE_VALUE == hFind) 
   { 
      MessageBox(hWnd, TEXT("No JPEG files found in folder"), TEXT("LEADTOOLS Demo"), MB_ICONERROR); 
      return; 
   } 
 
   TCHAR szFileOut[260] = TEXT(""); // Output file name   
   // Choose output file name  
   if (SUCCESS != GetTargetName(0, szFileOut, ARRAYSIZE(szFileOut))) 
      return; 
 
   //IltmmConvert* pConvert = NULL; 
   CoCreateInstance(CLSID_ltmmConvert, NULL, CLSCTX_INPROC_SERVER, IID_IltmmConvert, (void**)&pConvert); 
   if (!pConvert) 
      return; 
   //IltmmSampleSource* pSampleSource = NULL; 
   CoCreateInstance(CLSID_ltmmSampleSource, NULL, CLSCTX_INPROC_SERVER, IID_IltmmSampleSource, (void**)&pSampleSource); 
 
   //IltmmMediaTypeDisp* pMediaType = NULL; 
   CoCreateInstance(CLSID_ltmmMediaType, NULL, CLSCTX_INPROC_SERVER, IID_IltmmMediaTypeDisp, (void**)&pMediaType); 
   BSTR bstr = SysAllocString(ltmmMEDIATYPE_Video); 
   pMediaType->put_Type(bstr); 
   SysFreeString(bstr); 
 
   bstr = SysAllocString(ltmmMEDIASUBTYPE_RGB24); 
   pMediaType->put_Subtype(bstr); 
   SysFreeString(bstr); 
 
   bstr = SysAllocString(ltmmFORMAT_VideoInfo); 
   pMediaType->put_FormatType(bstr); 
   SysFreeString(bstr); 
 
   SetVideoCompressor(pConvert); 
 
   double framesPerSecond = 1.5; 
 
   const int imageWidth = 1024, imageHeight = 768; 
 
   VIDEOINFOHEADER vih = { 0 }; 
 
   vih.bmiHeader.biCompression = BI_RGB; 
   vih.bmiHeader.biBitCount = 24; 
   vih.bmiHeader.biSize = sizeof BITMAPINFOHEADER; 
   vih.bmiHeader.biWidth = imageWidth; 
   vih.bmiHeader.biHeight = imageHeight; 
   vih.bmiHeader.biPlanes = 1; 
   int BytesPerLine = ((imageWidth * 3 + 3) / 4) * 4; //use integer division to calculate byte padding to 4-byte multiples 
   int bmpSize = BytesPerLine * imageHeight; 
   vih.bmiHeader.biSizeImage = bmpSize; 
   vih.bmiHeader.biClrImportant = 0; 
   vih.AvgTimePerFrame = (REFERENCE_TIME)(10000000.0 / framesPerSecond); 
   vih.dwBitRate = (DWORD)(bmpSize * 8 * framesPerSecond); 
 
   SAFEARRAY sa = { 0 }; 
   VARIANT var; 
   sa.cbElements = sizeof(unsigned char); 
   sa.cDims = 1; 
   sa.fFeatures = (FADF_AUTO | FADF_FIXEDSIZE); 
   sa.pvData = &vih; 
   sa.rgsabound[0].cElements = sizeof vih; 
   VariantInit(&var); 
   V_VT(&var) = (VT_ARRAY | VT_UI1); 
   V_ARRAY(&var) = &sa; 
 
   pMediaType->SetFormatData(sizeof vih, var); 
   pMediaType->put_SampleSize(bmpSize); 
   pMediaType->put_FixedSizeSamples(VARIANT_TRUE); 
 
   // assign the source media type 
   pSampleSource->SetMediaType(pMediaType); 
 
   pConvert->put_SourceObject(pSampleSource); 
 
   bstr = SysAllocString(szFileOut); 
   pConvert->put_TargetFile(bstr); 
   SysFreeString(bstr); 
 
   pConvert->put_TargetFormat(ltmmConvert_TargetFormat_Avi); 
 
   pConvert->StartConvert(); 
   L_UCHAR* buf = new L_UCHAR[bmpSize]; 
 
   LARGE_INTEGER starttime; 
   LARGE_INTEGER stoptime; 
   LARGE_INTEGER mstarttime; 
   LARGE_INTEGER mstoptime; 
   starttime.QuadPart = 0; 
   mstarttime.QuadPart = 0; 
   int n = 1; 
   do // loop to get all images 
   { 
      TCHAR szImageFile[1024] = TEXT(""); // Input file full location and name 
      _tcscpy_s(szImageFile, ARRAYSIZE(szImageFile), szFolderIn); 
      _tcscat_s(szImageFile, ARRAYSIZE(szImageFile), TEXT("\\")); 
      _tcscat_s(szImageFile, ARRAYSIZE(szImageFile), FindFileData.cFileName); 
      TCHAR szProgress[1024]; 
      _stprintf_s(szProgress, ARRAYSIZE(szProgress), TEXT("Loading image number %d    "), n++); 
      HDC hDC = GetDC(hWnd); 
      TextOut(hDC, 50, 50, szProgress, _tcslen(szProgress)); 
      ReleaseDC(hWnd, hDC); 
 
      BITMAPHANDLE BmpLoad = { 0 }, BmpFrame = { 0 }; 
      // resize image and maintain aspect ratio 
      L_LoadBitmapResize(szImageFile, &BmpLoad, sizeof BITMAPHANDLE, 0, imageHeight, 24, SIZE_NORMAL, ORDER_BGR, NULL, NULL); 
      /* 
      L_LoadBitmap(szImageFile, &BmpLoad, sizeof BITMAPHANDLE, 24, ORDER_BGR, NULL, NULL); 
      int newWidth = imageHeight * BITMAPWIDTH(&BmpLoad) / BITMAPHEIGHT(&BmpLoad); 
      L_SizeBitmap(&BmpLoad, newWidth, imageHeight, SIZE_RESAMPLE); 
      */ 
      L_ChangeBitmapViewPerspective(NULL, &BmpLoad, sizeof BITMAPHANDLE, BOTTOM_LEFT); 
      L_CreateBitmap(&BmpFrame, sizeof BITMAPHANDLE, TYPE_CONV, imageWidth, imageHeight, 24, ORDER_BGR, NULL, BOTTOM_LEFT, NULL, 0); 
      L_FillBitmap(&BmpFrame, RGB(255, 160, 220)); 
      L_CombineBitmap(&BmpFrame, (BITMAPWIDTH(&BmpFrame) - BITMAPWIDTH(&BmpLoad)) / 2, 0, BITMAPWIDTH(&BmpLoad), BITMAPHEIGHT(&BmpLoad), &BmpLoad, 0, 0, CB_RAWCOMBINE | CB_OP_ADD | CB_DST_0, 0); 
      L_FreeBitmap(&BmpLoad); 
 
      //L_LoadBitmap(FindFileData.cFileName, &LtBmp, sizeof LtBmp, 24, ORDER_BGR, NULL, NULL); 
      // convert the frame number to text and draw it to the bitmap 
      IltmmMediaSampleDisp* pMediaSample = NULL; 
      pSampleSource->GetSampleBuffer(1000, &pMediaSample); 
 
      L_AccessBitmap(&BmpFrame); 
      L_GetBitmapRow(&BmpFrame, buf, 0, bmpSize); 
      L_ReleaseBitmap(&BmpFrame); 
      L_FreeBitmap(&BmpFrame); 
      // set the time in 100 nanoseconds (based on frame rate) 
      stoptime.QuadPart = starttime.QuadPart + vih.AvgTimePerFrame; 
      pMediaSample->SetTime(starttime.HighPart, starttime.LowPart, stoptime.HighPart, stoptime.LowPart); 
      // media time is equal to the frame number 
      mstoptime.QuadPart = mstarttime.QuadPart + 1; 
      pMediaSample->SetMediaTime(mstarttime.HighPart, mstarttime.LowPart, mstoptime.HighPart, mstoptime.LowPart); 
      // this is a sync point 
      pMediaSample->put_SyncPoint(VARIANT_TRUE); 
      // set the sample data 
      memset(&sa, 0, sizeof sa); 
      sa.cbElements = sizeof(unsigned char); 
      sa.cDims = 1; 
      sa.fFeatures = (FADF_AUTO | FADF_FIXEDSIZE); 
      sa.pvData = buf; 
      sa.rgsabound[0].cElements = bmpSize; 
      V_VT(&var) = (VT_ARRAY | VT_UI1); 
      V_ARRAY(&var) = &sa; 
 
      pMediaSample->SetData(bmpSize, var); 
 
      // send the sample downstream 
      pSampleSource->DeliverSample(1000, pMediaSample); 
 
      // adjust the next time values 
      starttime = stoptime; 
      mstarttime = mstoptime; 
 
      // release the sample buffer 
      pMediaSample->Release(); 
   } while(FindNextFile(hFind, &FindFileData) != 0); 
   delete[] buf; 
   InvalidateRect(hWnd, NULL, TRUE); 
   MessageBox(hWnd, TEXT("Finished creating AVI"), TEXT("LEADTOOLS Demo"), MB_ICONINFORMATION); 
   pSampleSource->DeliverEndOfStream(1000); 
   pConvert->StopConvert(); 
} 

Add Code of Helper Functions

Add three functions named GetTargetName, GetSourceFolder and SetVideoCompressor above the GenerateVideo function and add the following code to them:

L_INT GetTargetName(HWND hwnd, TCHAR* pszFileName, DWORD nLen) 
{ 
   OPENFILENAME OpenFileName = { 0 }; 
   OpenFileName.lStructSize = sizeof OPENFILENAME; 
   OpenFileName.hwndOwner = hwnd; 
   OpenFileName.lpstrFilter = TEXT("AVI File\0*.avi\0"); 
   OpenFileName.lpstrFile = pszFileName; 
   OpenFileName.nMaxFile = nLen; 
   OpenFileName.lpstrDefExt = TEXT("avi"); 
   OpenFileName.lpstrFileTitle = NULL; 
   OpenFileName.nMaxFileTitle = 0; 
   OpenFileName.lpstrInitialDir = NULL; 
   OpenFileName.Flags = OFN_OVERWRITEPROMPT; 
 
   // Show the File Save dialog box 
   if (!GetSaveFileName(&OpenFileName)) 
      return FAILURE; 
   return SUCCESS; 
} 
 
#include <shobjidl_core.h> 
L_INT GetSourceFolder(HWND hWnd, TCHAR* pszFolder, rsize_t  string_size) 
{ 
   L_INT nRet = FAILURE; 
   IFileOpenDialog* pFileOpenDialog = NULL; 
   IShellItem* pShellItem = NULL; 
   CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)&pFileOpenDialog); 
   if(pFileOpenDialog) 
   { 
      pFileOpenDialog->SetOptions(FOS_PICKFOLDERS); 
      if (SUCCEEDED(pFileOpenDialog->Show(hWnd))) 
      { 
         pFileOpenDialog->GetResult(&pShellItem); 
         LPWSTR pFolder = NULL; 
         pShellItem->GetDisplayName(SIGDN_FILESYSPATH, &pFolder); 
         if (pFolder) 
         { 
            _tcscpy_s(pszFolder, string_size, pFolder); 
            CoTaskMemFree(pFolder); 
            nRet = SUCCESS; 
         } 
      } 
   } 
   if (pShellItem) 
      pShellItem->Release(); 
   if (pFileOpenDialog) 
      pFileOpenDialog->Release(); 
   return nRet; 
} 
 
void SetVideoCompressor(IltmmConvert* pConvert) 
{ 
   // Select Motion JPEG video compression and disable interleaving because it reduces quality for still images 
   IltmmCompressors* pCompressors; 
   pConvert->get_VideoCompressors(&pCompressors); 
   long MCMP_MJPEG_CodecIndex = -1; 
   BSTR bstr = SysAllocString(L"@device:sw:{33D9A760-90C8-11D0-BD43-00A0C911CE86}\\LEAD MCMP/MJPEG Codec (2.0)"); 
   pCompressors->Find(bstr, &MCMP_MJPEG_CodecIndex); 
   SysFreeString(bstr); 
   if (MCMP_MJPEG_CodecIndex > -1) 
   { 
      pCompressors->put_Selection(MCMP_MJPEG_CodecIndex); 
      IUnknown* pMjpegCodec = NULL; 
      pConvert->GetSubObject(ltmmConvert_Object_VideoCompressor, &pMjpegCodec); 
      IMCMPEncoderOption* pOptions = NULL; 
      pMjpegCodec->QueryInterface(IID_IMCMPEncoderOption, (void**)&pOptions); 
      pMjpegCodec->Release(); 
      pOptions->put_Format(FORMAT_MJPEG_411); 
      pOptions->put_Flags(FLAGS_INTERLEAVE_NEVER); 
      pOptions->Release(); 
   } 
   pCompressors->Release(); 
} 

Run the Project

Run the project by pressing F5, or by selecting Debug -> Start Debugging.

If the steps are followed correctly, the application runs and converts all the JPEG images from a user-selected folder to a video file.

Wrap-up

This tutorial showed how to produce a video file from still images using the ltmmConvert Object.

See Also

Help Version 22.0.2024.3.20
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.


Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.