2016年10月13日木曜日

32bitDIBから無圧縮AVI2.0 (2)





実際に出力した動画ファイル
[161013]avi20_output.avi
filesize: 19.8MB




 ざっくりとしたファイルフォーマット図。
青字が記述に使用する構造体、緑字がその構造体のサイズになります。
自分がプログラムを組む際どこに何を書き込むのかよくわからなかったのでまとめました。
矢印は各インデックスがさすデータ位置で、
チャンク先頭だったりデータ先頭だったりするようです。
 WindowsMediaPlayerで再生するには、
AVIStreamHeaderのdwLengthをファイル全体のトータルフレーム数にしないとRIFF-AVI部分を再生後にエラーがでるっぽいです。
仕様ではRIFF-AVI内のフレーム数を指定するはずです。



ソースファイル
file: [20161013]AVI20.cpp
 前回のソースコードはどうもチャンクサイズが間違ってるっぽいので新しく書き直しました。
ファイル作成後ヘッダー部分を飛ばし、画像データを書き込みつつRIFFリストを追記更新。
記録終了時にヘッダーを書きにファイル先端に戻ってヘッダー記述、という流れになります。


#include <windows.h>
#include <vector>



const int WINDOW_WIDTH = 640 / 2;
const int WINDOW_HEIGHT = 480/ 2;
const int TIMER_ID = 1000;
const int FPS = 30;


class DIB32
{
public:
 class PutColorAdd
 {
 public:
  static inline void update( DWORD& dest, DWORD src )
  {
   DWORD color = (dest & 0x00fefefe) + (src & 0x00fefefe);
   DWORD mask = color & 0x01010100;
   dest = (dest&0xFF000000) | color | (mask - (mask >> 8));
  }
 };




 DIB32()
  : m_pixel( NULL )
 {
  ZeroMemory( &m_bmi, sizeof(m_bmi) );
 }
 
 virtual ~DIB32()
 {
  release();
 }
 
 virtual bool create( LONG width, LONG height )
 {
  if ( width <= 0 || height <= 0 ) return false;
  release();
 
  m_pixel = new DWORD[ width * height ];
  if ( !m_pixel ) return false;
 
  m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  m_bmi.bmiHeader.biWidth = width;
  m_bmi.bmiHeader.biHeight = height;
  m_bmi.bmiHeader.biPlanes = 1;
  m_bmi.bmiHeader.biBitCount = 32;
  m_bmi.bmiHeader.biSizeImage = width * height * 4;
  m_bmi.bmiHeader.biCompression = BI_RGB;
  m_bmi.bmiHeader.biXPelsPerMeter = 3780;
  m_bmi.bmiHeader.biYPelsPerMeter = 3780;
 
  return true;
 }
 
 /** create from HBITMAP */
 bool create( LPCTSTR fileName )
 {
  HBITMAP hBmp = static_cast<HBITMAP>( LoadImage( NULL, fileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE ) );
  if ( !hBmp ) return false;

  BITMAP bm = {0};
  GetObject( hBmp, sizeof(BITMAP), &bm );

  // create DC
  HDC hdc = CreateCompatibleDC( NULL );
  if ( !hdc ) return false;

  // create buf
  if ( !create( bm.bmWidth, bm.bmHeight ) )
  {
   DeleteDC( hdc );
   return false;
  }

  // copy
  GetDIBits( hdc, hBmp, 0, bm.bmHeight, m_pixel, &m_bmi, DIB_RGB_COLORS );

  DeleteDC( hdc );
  DeleteObject( hBmp );

  return true;
 }
 
 virtual void release()
 {
  if ( m_pixel )
  {
   delete [] m_pixel;
   m_pixel = NULL;
  }
  ZeroMemory( &m_bmi, sizeof(m_bmi) );
 }
 
 bool render( HDC hdc, HWND hWnd ) const
 {
  RECT rect;
  GetClientRect( hWnd, &rect );
  return GDI_ERROR != StretchDIBits(
   hdc, 0, 0, rect.right, rect.bottom,
   0, 0, getWidth(), getHeight(),
   m_pixel, &m_bmi, DIB_RGB_COLORS, SRCCOPY );
 }
 
 
 /** render to dib */
 template < class T >
 bool render( DIB32& dest,
  LONG destX, LONG destY, LONG destWidth, LONG destHeight,
  LONG srcX, LONG srcY, LONG srcWidth, LONG srcHeight ) const
 {
  LONG width = abs( destWidth );
  LONG height = abs( destHeight );
  const LONG absDestWidth = width;
  const LONG absDestHeight = height;
  const LONG absSrcWidth = abs( srcWidth );
  const LONG absSrcHeight = abs( srcHeight );
  const float magniX = static_cast<float>(width) / static_cast<float>(srcWidth);
  const float magniY = static_cast<float>(height) / static_cast<float>(srcHeight);
  LONG sw = static_cast<LONG>( static_cast<float>(getWidth()) * magniX );
  LONG sh = static_cast<LONG>( static_cast<float>(getHeight()) * magniY );
  LONG sx = static_cast<LONG>( static_cast<float>(srcX) * magniX );
  LONG sy = static_cast<LONG>( static_cast<float>(srcY) * magniY );
  
  if ( !cliping( destX, destY, width, height, sx, sy, dest.getWidth(), dest.getHeight(), sw, sh ) )
  {
   return false;
  }
  
  
  const int shift = 14;
  const LONG addX = (absSrcWidth << shift) / absDestWidth;
  const LONG addY = (absSrcHeight << shift) / absDestHeight;

  // src*magniだったsxを元に戻しつつ、固定小数点数に
  LONG fsx = static_cast<LONG>( static_cast<float>(sx << shift) / magniX );
  LONG fsy = static_cast<LONG>( static_cast<float>(sy << shift) / magniY );
   
  LPDWORD destLine = dest.m_pixel + ((dest.getHeight()-1)-destY) * dest.getWidth() + destX;
  for (LONG y=0; y<height; ++y)
  {
   LPDWORD destPixel = destLine;
   LPDWORD srcLine = m_pixel + ((getHeight()-1)-(fsy>>shift)) * getWidth();
   LONG tmpx = fsx;
   for (LONG x=0; x<width; ++x)
   {
    T::update( *destPixel++, srcLine[ tmpx >> shift ] );
    tmpx += addX;
   }

   fsy += addY;
   destLine -= dest.getWidth();
  }
  
  
  return true;
 }

 /** cliping */
 static inline bool cliping(
  LONG& destX, LONG& destY, LONG& width, LONG& height, LONG& srcX, LONG& srcY,
  const LONG destWidth, const LONG destHeight, const LONG srcWidth, const LONG srcHeight )
 {
  // 左端クリッピング
  if ( destX < 0 ) { width += destX; srcX -= destX; destX = 0; }
  if ( srcX < 0 ) { width += srcX; destX -= srcX; srcX = 0; }

  // 右端
  if ( destX + width > destWidth ) { width -= ((destX+width)-destWidth); }
  if ( srcX + width > srcWidth ) { width -= ((srcX+width)-srcWidth); }

  // 上
  if ( destY < 0 ) { height += destY; srcY -= destY; destY = 0; }
  if ( srcY < 0 ) { height += srcY; destY -= srcY; srcY = 0; }

  // 下
  if ( destY + height > destHeight ) { height -= ((destY+height)-destHeight); }
  if ( srcY + height > srcHeight ) { height -= ((srcY+height)-srcHeight); }

  return ((width > 0) & (height > 0));
 }

 /** abs */
 template<class T> static T abs(const T& a) { return ((a) < 0 ? -(a) : (a)); }
 
 LONG getWidth() const { return m_bmi.bmiHeader.biWidth; }
 LONG getHeight() const { return m_bmi.bmiHeader.biHeight; }
 const LPDWORD getPixelAddr() const { return m_pixel; }
 LPDWORD getPixelAddr() { return m_pixel;  }

 
 const BITMAPINFO& getBMPInfo() { return m_bmi; }

protected:

 LPDWORD m_pixel;
 BITMAPINFO m_bmi;
};
 
 


/*
AVISTREAMHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352263.aspx>
AVIMAINHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352261.aspx>
BITMAPINFOHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352308.aspx>
AVISUPERINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff625871(v=vs.85).aspx>
AVISTDINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff625869(v=vs.85).aspx>
AVIOLDINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/dd318181(v=vs.85).aspx>
*/

 
class AVIRecorder
{
public:
#pragma pack(1)
 struct LIST
 {
  DWORD dwList;
  DWORD dwSize;
  DWORD dwFourCC;
 };

 struct CHUNK
 {
  DWORD dwFourCC;
  DWORD dwSize;
 };

 struct MainAVIHeader
 {

  DWORD  dwMicroSecPerFrame; // frame display rate (or 0L)
  DWORD  dwMaxBytesPerSec; // max. transfer rate
  DWORD  dwPaddingGranularity; // pad to multiples of this
  // size; normally 2K.
  DWORD  dwFlags;  // the ever-present flags
#define AVIF_HASINDEX        0x00000010 // Index at end of file?
#define AVIF_MUSTUSEINDEX    0x00000020
#define AVIF_ISINTERLEAVED   0x00000100
#define AVIF_TRUSTCKTYPE     0x00000800 // Use CKType to find key frames
#define AVIF_WASCAPTUREFILE  0x00010000
#define AVIF_COPYRIGHTED     0x00020000
  DWORD  dwTotalFrames;  // # frames in file
  DWORD  dwInitialFrames;
  DWORD  dwStreams;
  DWORD  dwSuggestedBufferSize;

  DWORD  dwWidth;
  DWORD  dwHeight;

  DWORD  dwReserved[4];
 };

 struct AVIStreamHeader
 {
  FOURCC  fccType;
  FOURCC  fccHandler;
  DWORD  dwFlags; /* Contains AVITF_* flags */
  WORD  wPriority;
  WORD  wLanguage;
  DWORD  dwInitialFrames;
  DWORD  dwScale; 
  DWORD  dwRate; /* dwRate / dwScale == samples/second */
  DWORD  dwStart;
  DWORD  dwLength; /* In units above... */
  DWORD  dwSuggestedBufferSize;
  DWORD  dwQuality;
  DWORD  dwSampleSize;
  //RECT  rcFrame;
  struct
  {
   short int left;
   short int top;
   short int right;
   short int bottom;
  } rcFrame;
 };

/* Flags for index */
#define AVIIF_LIST          0x00000001L // chunk is a 'LIST'
#define AVIIF_KEYFRAME      0x00000010L // this frame is a key frame.
#define AVIIF_FIRSTPART     0x00000020L // this frame is the start of a partial frame.
#define AVIIF_LASTPART      0x00000040L // this frame is the end of a partial frame.
#define AVIIF_MIDPART       (AVIIF_LASTPART|AVIIF_FIRSTPART)

#define AVIIF_NOTIME     0x00000100L // this frame doesn't take any time
#define AVIIF_COMPUSE       0x0FFF0000L // these bits are for compressor use

 // 1022 -> 16384 byte
 // single -> 32 + 16*n byte
 struct AVISUPERINDEX
 {
  FOURCC fcc;
  DWORD cd;
  WORD wLongsPerEntry;
  BYTE bIndexSubType;
  BYTE bIndexType;
  DWORD nEntriesInUse;
  DWORD dwChunkID;
  DWORD dwReserved[3];
 };

 struct AVISUPERINDEX_ENTRY
 {
  DWORDLONG qwOffset;
  DWORD dwSize;
  DWORD dwDuration;
 };

#define AVI_INDEX_OF_INDEXES       0x00
#define AVI_INDEX_OF_CHUNKS        0x01
#define AVI_INDEX_OF_TIMED_CHUNKS  0x02
#define AVI_INDEX_OF_SUB_2FIELD    0x03
#define AVI_INDEX_IS_DATA          0x80

 struct AVISTDINDEX
 {
  FOURCC fcc;
  DWORD cd;
  WORD wLongsPerEntry;
  BYTE bIndexSubType;
  BYTE bIndexType;
  DWORD nEntriesInUse;
  DWORD dwChunkID;
  DWORDLONG qwBaseOffset;
  DWORD dwReserved;  
 };

 struct AVISTDINDEX_ENTRY
 {
  DWORD dwOffset;
  DWORD dwSize;
 };

 struct AVIINDEXENTRY
 {
  DWORD  ckid;
  DWORD  dwFlags;
  DWORD  dwChunkOffset;  // Position of chunk
  DWORD  dwChunkLength;  // Length of chunk
 };

 struct AVIEXTHEADER
 {
  FOURCC  fcc;                    // 'dmlh'
  DWORD   cb;                     // size of this structure -8
  DWORD   dwGrandFrames;          // total number of frames in the file
  DWORD   dwFuture[61];           // to be defined later
 };
#pragma pack()

 enum
 {
  MAX_CHUNK_SIZE = 8 * 1024 * 1024,  //< 1チャンクの最大サイズ
  SUPER_INDEX_ENTRY_SIZE = 32 * 1024,  //< SuperIndexEntryを確保するサイズ。
  PADDING_GRANULARITY = 2048,
 };



 AVIRecorder()
  : m_file( INVALID_HANDLE_VALUE )
  , m_nowRiffFrame( 0 )
  , m_totalFrame( 0 )
  , m_numOfRiff( 0 )
  , m_nowRiffSize( 0 )
  , m_mostLargeImageSize( 0 )
  , m_maxNumOfRiff( 0 )
  , m_moviChunkOffset( 0 )
  , m_isAVIX( false )
  , m_width( 0 )
  , m_height( 0 )
  , m_imageSize( 0 )
  , m_fps( 0 )
  , m_riffAVIChunkFrame( 0 )
  , m_riffAVIChunkSize( 0 )
 {
  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
 }

 ~AVIRecorder()
 {
  close();
 }



 /** create */
 bool create( LPCTSTR fileName, DWORD width, DWORD height, DWORD fps )
 {
  close();

  m_file = CreateFile( fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  if ( isOpen() )
  {
   m_width = width;
   m_height = height;
   m_fps = fps;
   m_imageSize = width * height * 4;

   // 何も書き込んでないので0のままだけど、一応現在RiffListの先端位置を記憶
   ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
   m_nowRiffFileTopPos.LowPart = SetFilePointer( m_file, 0, &m_nowRiffFileTopPos.HighPart, FILE_CURRENT );

   // ファイル作成時にすっとばすヘッダ分の領域
   const DWORD preFileHeaderSkipSize =
    sizeof(LIST)              //< RIFF-AVI LIST
     + sizeof(LIST)             //< hdrl List
      + sizeof(CHUNK) + sizeof(MainAVIHeader)      //< avih Chunk + MainAVIHeader
      + sizeof(LIST)            //< list-strl
       + sizeof(CHUNK) + sizeof(AVIStreamHeader)    //< strh Chunk + AVIStreamHeader
       + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)    //< strf Chunk + BITMAPINFOHEADER
       + sizeof(AVISUPERINDEX) + SUPER_INDEX_ENTRY_SIZE  //< AVISUPERINDEX + SUPER_INDEX_ENTRY_SIZE
     + sizeof(LIST)            //< odml List
      + sizeof(AVIEXTHEADER)         //< AVIEXTHEADER
     + sizeof(LIST);            //< movi LIST


   DWORD fileHeaderSkipSize = preFileHeaderSkipSize;

   if ( PADDING_GRANULARITY )
   {
    const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
    fileHeaderSkipSize = preFileHeaderSkipSize + (PADDING_GRANULARITY - (preFileHeaderSkipSize % PaddingGranularity));
   }

   // 'movi'は直後に追記するのでその分は省く
   fileHeaderSkipSize -= sizeof(LIST);

   
   // ヘッダ部分をスキップ
   SetFilePointer( m_file, fileHeaderSkipSize, NULL, FILE_BEGIN );

   // 格納可能RIFF数
   m_maxNumOfRiff = SUPER_INDEX_ENTRY_SIZE / sizeof(AVISUPERINDEX_ENTRY);
   m_numOfRiff = 1;
   m_nowRiffSize = fileHeaderSkipSize;
   


   // 'movi'開始位置記憶。movi-LISTのサイズを書き換えに戻ってくる
   ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
   m_moviListPos.LowPart = SetFilePointer( m_file, 0, &m_moviListPos.HighPart, FILE_CURRENT );
  

   // 'movi'LIST 書き込み
   {
    LIST movi;
    movi.dwList = mmioFOURCC('L','I','S','T');
    movi.dwSize = (MAX_CHUNK_SIZE - fileHeaderSkipSize) - 8;
    movi.dwFourCC = mmioFOURCC('m','o','v','i');

    DWORD writeSize;
    WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );

    m_nowRiffSize += sizeof(LIST);
   }
 
   m_moviChunkOffset = 0;

   return true;
  }

  return false;
 }

 void update( const DIB32& image )
 {
  if ( isOpen() )
  {
   
   // 2048境界に合わせてJUNKつめ
   {
    const DWORD junkSize = writePaddingJUNK();
    m_nowRiffSize += junkSize;
    m_moviChunkOffset += junkSize;
   }
   

   // 画像データ書き込み
   CHUNK chunk;
   chunk.dwFourCC = mmioFOURCC('0','0','d','b');
   chunk.dwSize = m_imageSize;

   DWORD writeSize;
   WriteFile( m_file, &chunk, sizeof(chunk), &writeSize, NULL );
   WriteFile( m_file, const_cast<LPDWORD>(image.getPixelAddr()), chunk.dwSize, &writeSize, NULL );


   // ix00用にデータを詰める
   {
    AVISTDINDEX_ENTRY entry = {0};
    entry.dwSize = chunk.dwSize;
    entry.dwOffset = (sizeof(LIST)-8) + sizeof(CHUNK) + m_moviChunkOffset; //< データ位置。

    m_aviStdIndexEntryList.push_back( entry );
   }

   // idx1用にデータを詰める
   if ( !m_isAVIX )
   {
    AVIINDEXENTRY aviIndexEntry = {0};
    aviIndexEntry.ckid = mmioFOURCC('0','0','d','b');
    aviIndexEntry.dwFlags = AVIIF_KEYFRAME;
    aviIndexEntry.dwChunkLength = chunk.dwSize;      //< sizeof(CHUNK)+chunk.dwSizeじゃないらしい。なんで
    aviIndexEntry.dwChunkOffset = (sizeof(LIST)-8) + m_moviChunkOffset; //< 'movi'チャンクヘッダからの距離。-8する意味が分からないが数値はこうっぽい

    m_aviIndexEntryList.push_back( aviIndexEntry );
   }

   

   m_mostLargeImageSize = (std::max)( m_mostLargeImageSize, chunk.dwSize );
   m_nowRiffSize += sizeof(CHUNK) + chunk.dwSize;
   m_nowRiffFrame++;
   m_totalFrame++;
   m_moviChunkOffset += sizeof(CHUNK) + chunk.dwSize;


   // チャンクが指定サイズに到達しそうなら次のチャンクに移る
   const DWORD freeChunkSize = MAX_CHUNK_SIZE - (m_nowRiffSize + sizeof(AVISTDINDEX) + m_nowRiffFrame*sizeof(AVIINDEXENTRY));
   if ( freeChunkSize < m_mostLargeImageSize )
   {
    if ( m_numOfRiff >= m_maxNumOfRiff )
    {
     close();
    }
    else
    {
     nextRIFF();
    }
   }
  }
 }

 /** close */
 void close()
 {
  if ( isOpen() )
  {
   exitRIFF();

   // close
   CloseHandle( m_file );
   m_file = INVALID_HANDLE_VALUE;
  }



  m_nowRiffFrame = 0;
  m_totalFrame = 0;
  m_numOfRiff = 0;
  m_nowRiffSize = 0;
  m_mostLargeImageSize = 0;
  m_maxNumOfRiff = 0;
  m_moviChunkOffset = 0;


  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );

  m_isAVIX = false;
  m_riffAVIChunkFrame = 0;
  m_riffAVIChunkSize = 0;
  m_fps = 0;
  m_width = 0;
  m_height = 0;
  m_imageSize = 0;

  m_superIndexEntryList.clear();
  m_aviIndexEntryList.clear();
  m_aviStdIndexEntryList.clear();
 }

 /** isOpen */
 bool isOpen() const { return m_file!=INVALID_HANDLE_VALUE; }


 DWORD getTotalFrame() const { return m_totalFrame; }
 DWORD getNowRiffFrame() const { return m_nowRiffFrame; }


private:
 void nextRIFF()
 {
  // AVISUPERINDEX_ENTRY蓄積。各チャンクのix00の位置を示す
  {
   LARGE_INTEGER nowPos = {0};
   nowPos.LowPart = SetFilePointer( m_file, 0, &nowPos.HighPart, FILE_CURRENT );

   AVISUPERINDEX_ENTRY entry = {0};
   entry.dwSize = sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   entry.dwDuration = m_aviStdIndexEntryList.size();
   entry.qwOffset = nowPos.QuadPart;
   m_superIndexEntryList.push_back( entry );
  }


  // ix00
  {
   AVISTDINDEX standardIndex = {0};
   standardIndex.fcc = mmioFOURCC('i','x','0','0');
   standardIndex.cd = (sizeof(AVISTDINDEX) - 8) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   standardIndex.wLongsPerEntry = 2;  //< must be 2
   standardIndex.bIndexSubType = 0;  //< must be 0
   standardIndex.bIndexType = AVI_INDEX_OF_CHUNKS;  //< AVI_INDEX_OF_CHUNKS == 1
   standardIndex.nEntriesInUse = m_aviStdIndexEntryList.size();
   standardIndex.dwChunkID = mmioFOURCC('0','0','d','b');
   standardIndex.qwBaseOffset = m_moviListPos.QuadPart;

   DWORD writeSize;
   WriteFile( m_file, &standardIndex, sizeof(standardIndex), &writeSize, NULL );

   // entry
   if ( !m_aviStdIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviStdIndexEntryList[0], sizeof(m_aviStdIndexEntryList[0])*m_aviStdIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_aviStdIndexEntryList.size();
  }


  // idx1
  if ( !m_isAVIX )
  {
   // write index entry
   CHUNK index;
   index.dwFourCC = mmioFOURCC('i','d','x','1');
   index.dwSize = sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size(); 

   DWORD writeSize;
   WriteFile( m_file, &index, sizeof(index), &writeSize, NULL );

   // write entry
   if ( !m_aviIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviIndexEntryList[0], sizeof(m_aviIndexEntryList[0])*m_aviIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(CHUNK) +  sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size();
  }


  if ( !m_isAVIX )
  {
   m_riffAVIChunkSize = m_nowRiffSize;
   m_riffAVIChunkFrame = m_nowRiffFrame;
  }


  {
   // 現在位置記憶
   LARGE_INTEGER nowFilePos = {0};
   nowFilePos.LowPart = SetFilePointer( m_file, 0, &nowFilePos.HighPart, FILE_CURRENT );

   // ヘッダ位置に戻ってサイズ情報を更新する
   if ( m_isAVIX )
   {
    // 現在チャンクのヘッダ位置に移動
    SetFilePointer( m_file, m_nowRiffFileTopPos.LowPart, &m_nowRiffFileTopPos.HighPart, FILE_BEGIN );
   
    // ヘッダ書き換え
    LIST avix;
    avix.dwList = mmioFOURCC('R','I','F','F');
    avix.dwSize = m_nowRiffSize - 8;
    avix.dwFourCC = mmioFOURCC('A','V','I','X');

    DWORD writeSize;
    WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );
   }

   // moviチャンクのサイズを正しい値に書き換える
   {
    // movi位置に移動
    SetFilePointer( m_file, m_moviListPos.LowPart, &m_moviListPos.HighPart, FILE_BEGIN );

    LIST movi;
    movi.dwList = mmioFOURCC('L','I','S','T');
    movi.dwSize = (sizeof(LIST) - 8) + m_moviChunkOffset + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
    movi.dwFourCC = mmioFOURCC('m','o','v','i');

    DWORD writeSize;
    WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );
   }

   // もとの位置に移動
   SetFilePointer( m_file, nowFilePos.LowPart, &nowFilePos.HighPart, FILE_BEGIN );
  }


  m_numOfRiff++;
  m_nowRiffSize = 0;
  m_nowRiffFrame = 0;
  m_moviChunkOffset = 0;

  m_aviIndexEntryList.clear();
  m_aviStdIndexEntryList.clear();

  m_isAVIX = true;

  // ヘッダの位置記憶
  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  m_nowRiffFileTopPos.LowPart = SetFilePointer( m_file, 0, &m_nowRiffFileTopPos.HighPart, FILE_CURRENT );

  // RIFF-AVIX
  {
   LIST avix;
   avix.dwList = mmioFOURCC('R','I','F','F');
   avix.dwSize = MAX_CHUNK_SIZE;    //< 後で書き換えに来るけどとりあえず最大値
   avix.dwFourCC = mmioFOURCC('A','V','I','X');

   DWORD writeSize;
   WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );

   m_nowRiffSize += sizeof(LIST);
  }

  // JUNK
  {
   const DWORD junkSize = writePaddingJUNK();
   m_nowRiffSize += junkSize;
  }


  // ヘッダの位置記憶
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
  m_moviListPos.LowPart = SetFilePointer( m_file, 0, &m_moviListPos.HighPart, FILE_CURRENT );

  // 'movi'LIST 書き込み
  {
   LIST movi;
   movi.dwList = mmioFOURCC('L','I','S','T');
   movi.dwSize = (MAX_CHUNK_SIZE - m_nowRiffSize) - 8;
   movi.dwFourCC = mmioFOURCC('m','o','v','i');

   DWORD writeSize;
   WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );

   m_nowRiffSize += sizeof(LIST);
  }
 }


 void exitRIFF()
 {
  // AVISUPERINDEX_ENTRY蓄積。各チャンクのix00の位置を示す
  {
   LARGE_INTEGER nowPos = {0};
   nowPos.LowPart = SetFilePointer( m_file, 0, &nowPos.HighPart, FILE_CURRENT );

   AVISUPERINDEX_ENTRY entry = {0};
   entry.dwSize = sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   entry.dwDuration = m_aviStdIndexEntryList.size();
   entry.qwOffset = nowPos.QuadPart;
   m_superIndexEntryList.push_back( entry );
  }
  
  // ix00
  {
   AVISTDINDEX standardIndex = {0};
   standardIndex.fcc = mmioFOURCC('i','x','0','0');
   standardIndex.cd = (sizeof(AVISTDINDEX) - 8) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   standardIndex.wLongsPerEntry = 2;  //< must be 2
   standardIndex.bIndexSubType = 0;  //< must be 0
   standardIndex.bIndexType = AVI_INDEX_OF_CHUNKS;  //< AVI_INDEX_OF_CHUNKS == 1
   standardIndex.nEntriesInUse = m_aviStdIndexEntryList.size();
   standardIndex.dwChunkID = mmioFOURCC('0','0','d','b');
   standardIndex.qwBaseOffset = m_moviListPos.QuadPart;

   DWORD writeSize;
   WriteFile( m_file, &standardIndex, sizeof(standardIndex), &writeSize, NULL );

   // entry
   if ( !m_aviStdIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviStdIndexEntryList[0], sizeof(m_aviStdIndexEntryList[0])*m_aviStdIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_aviStdIndexEntryList.size();
  }

  // idx1
  if ( !m_isAVIX )
  {
   // write index header
   CHUNK index;
   index.dwFourCC = mmioFOURCC('i','d','x','1');
   index.dwSize = sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size(); 

   DWORD writeSize;
   WriteFile( m_file, &index, sizeof(index), &writeSize, NULL );

   // write entry
   if ( !m_aviIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviIndexEntryList[0], sizeof(m_aviIndexEntryList[0])*m_aviIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(CHUNK) +  sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size();
  }


  // まだAVI-RIFFだったらAVI-RIFFでのサイズとフレーム数記憶
  if ( !m_isAVIX )
  {
   m_riffAVIChunkSize = m_nowRiffSize;
   m_riffAVIChunkFrame = m_nowRiffFrame;
  }

  // ヘッダ位置に戻ってサイズ情報を更新する
  if ( m_isAVIX )
  {
   // 現在チャンクのヘッダ位置に移動
   SetFilePointer( m_file, m_nowRiffFileTopPos.LowPart, &m_nowRiffFileTopPos.HighPart, FILE_BEGIN );
   
   // ヘッダ書き換え
   LIST avix;
   avix.dwList = mmioFOURCC('R','I','F','F');
   avix.dwSize = m_nowRiffSize - 8;
   avix.dwFourCC = mmioFOURCC('A','V','I','X');

   DWORD writeSize;
   WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );
  }

  // moviチャンクのサイズを正しい値に書き換える
  {
   // movi位置に移動
   SetFilePointer( m_file, m_moviListPos.LowPart, &m_moviListPos.HighPart, FILE_BEGIN );

   LIST movi;
   movi.dwList = mmioFOURCC('L','I','S','T');
   movi.dwSize = (sizeof(LIST) - 8) + m_moviChunkOffset + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   movi.dwFourCC = mmioFOURCC('m','o','v','i');

   DWORD writeSize;
   WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );
  }



  // AVI header
  {
   SetFilePointer( m_file, 0, NULL, FILE_BEGIN );

   // write avi header
   // RIFF-AVI LIST
   {
    LIST aviList;
    aviList.dwList = mmioFOURCC('R','I','F','F');
    aviList.dwSize = m_riffAVIChunkSize - 8;
    aviList.dwFourCC = mmioFOURCC('A','V','I',' ');

    DWORD writeSize;
    WriteFile( m_file, &aviList, sizeof(aviList), &writeSize, NULL );
   }

   // LIST-hdrl
   {
    LIST hdrlList;
    hdrlList.dwList = mmioFOURCC('L','I','S','T');
    hdrlList.dwSize = (sizeof(LIST) - 8)
     + sizeof(CHUNK) + sizeof(MainAVIHeader)
     + sizeof(LIST)
      + sizeof(CHUNK) + sizeof(AVIStreamHeader)
      + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)
      + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size()
     + sizeof(LIST)
      + sizeof(AVIEXTHEADER);
    hdrlList.dwFourCC = mmioFOURCC('h','d','r','l');

    DWORD writeSize;
    WriteFile( m_file, &hdrlList, sizeof(hdrlList), &writeSize, NULL );
   }

   // avih chunk
   {
    CHUNK avihChunk;
    avihChunk.dwFourCC = mmioFOURCC('a','v','i','h');
    avihChunk.dwSize = sizeof(MainAVIHeader);

    DWORD writeSize;
    WriteFile( m_file, &avihChunk, sizeof(avihChunk), &writeSize, NULL );
   }

   // MainAVIHeader
   {
    MainAVIHeader mainAVIHeader = {0};

    
    mainAVIHeader.dwMaxBytesPerSec = m_mostLargeImageSize * m_fps;
    mainAVIHeader.dwMicroSecPerFrame = (1000*1000) / m_fps;
    mainAVIHeader.dwPaddingGranularity = PADDING_GRANULARITY;
    mainAVIHeader.dwFlags = AVIF_TRUSTCKTYPE | AVIF_HASINDEX; //<< 2064
    mainAVIHeader.dwTotalFrames = m_riffAVIChunkFrame;
    mainAVIHeader.dwInitialFrames = 0;
    mainAVIHeader.dwStreams = 1;
    mainAVIHeader.dwSuggestedBufferSize = (m_mostLargeImageSize + sizeof(CHUNK));
    mainAVIHeader.dwWidth = m_width;
    mainAVIHeader.dwHeight = m_height;
    
    DWORD writeSize;
    WriteFile( m_file, &mainAVIHeader, sizeof(mainAVIHeader), &writeSize, NULL );
   }

   // LIST-strl
   {
    LIST strlList;
    strlList.dwList = mmioFOURCC('L','I','S','T');
    strlList.dwSize = (sizeof(LIST) - 8)
      + sizeof(CHUNK) + sizeof(AVIStreamHeader)
      + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)
      + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size();
    strlList.dwFourCC = mmioFOURCC('s','t','r','l');

    DWORD writeSize;
    WriteFile( m_file, &strlList, sizeof(strlList), &writeSize, NULL );
   }

   // strh chunk
   {
    CHUNK strhChunk;
    strhChunk.dwFourCC = mmioFOURCC('s','t','r','h');
    strhChunk.dwSize = sizeof(AVIStreamHeader);

    DWORD writeSize;
    WriteFile( m_file, &strhChunk, sizeof(strhChunk), &writeSize, NULL );
   }

   // AVIStreamHeader
   {
    AVIStreamHeader streamHeader = {0};
    streamHeader.fccType = mmioFOURCC('v','i','d','s');
    streamHeader.fccHandler = mmioFOURCC('D','I','B',' ');
    streamHeader.dwFlags = 0;
    streamHeader.wPriority = 0;
    streamHeader.wLanguage = 0;
    streamHeader.dwInitialFrames = 0;
    streamHeader.dwScale = 1000;
    streamHeader.dwRate = m_fps*1000;
    streamHeader.dwStart = 0;
    streamHeader.dwLength = /*m_riffAVIChunkFrame*/m_totalFrame;  //< 納得いかないがWMPだと全体のフレーム数でないと途中で落ちる
    streamHeader.dwSuggestedBufferSize = m_imageSize;
    streamHeader.dwQuality = -1;
    streamHeader.dwSampleSize = m_imageSize;
    streamHeader.rcFrame.left = 0;
    streamHeader.rcFrame.top = 0;
    streamHeader.rcFrame.right = m_width;
    streamHeader.rcFrame.bottom = m_height;
    DWORD writeSize;
    WriteFile( m_file, &streamHeader, sizeof(streamHeader), &writeSize, NULL );
   }

   // strf chunk
   {
    CHUNK strfChunk;
    strfChunk.dwFourCC = mmioFOURCC('s','t','r','f');
    strfChunk.dwSize = sizeof(BITMAPINFOHEADER);

    DWORD writeSize;
    WriteFile( m_file, &strfChunk, sizeof(strfChunk), &writeSize, NULL );
   }

   // BITMAPINFOHEADER
   {
    BITMAPINFOHEADER bmpInfoHeader = {0};
    bmpInfoHeader.biSize = sizeof(bmpInfoHeader);
    bmpInfoHeader.biWidth = m_width;
    bmpInfoHeader.biHeight = m_height;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = 32;
    bmpInfoHeader.biCompression = BI_RGB; //< =0
    bmpInfoHeader.biSizeImage = m_imageSize;
    bmpInfoHeader.biXPelsPerMeter = 3780;
    bmpInfoHeader.biYPelsPerMeter = 3780;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;

    DWORD writeSize;
    WriteFile( m_file, &bmpInfoHeader, sizeof(bmpInfoHeader), &writeSize, NULL );
   }

   // AVISUPERINDEX + entry
   {
    
    AVISUPERINDEX aviSuperIndex = {0};
    aviSuperIndex.fcc = mmioFOURCC('i','n','d','x');
    aviSuperIndex.cd = (sizeof(AVISUPERINDEX) - 8) + sizeof(AVISUPERINDEX_ENTRY)*m_superIndexEntryList.size();
    aviSuperIndex.wLongsPerEntry = sizeof(AVISUPERINDEX_ENTRY) / 4;  //< must be 4
    aviSuperIndex.bIndexType = AVI_INDEX_OF_INDEXES;
    aviSuperIndex.bIndexSubType = 0;
    aviSuperIndex.nEntriesInUse = m_superIndexEntryList.size();
    aviSuperIndex.dwChunkID = mmioFOURCC('0','0','d','b');
    
    DWORD writeSize;
    WriteFile( m_file, &aviSuperIndex, sizeof(aviSuperIndex), &writeSize, NULL );
    
    // entry
    if ( !m_superIndexEntryList.empty() )
    {
     WriteFile( m_file, &m_superIndexEntryList[0], sizeof(AVISUPERINDEX_ENTRY)*m_superIndexEntryList.size(), &writeSize, NULL );
    }
   }

   // LIST-odml
   {
    LIST odmlList;
    odmlList.dwList = mmioFOURCC('L','I','S','T');
    odmlList.dwSize = (sizeof(LIST) - 8)
      + sizeof(AVIEXTHEADER);
    odmlList.dwFourCC = mmioFOURCC('o','d','m','l');

    DWORD writeSize;
    WriteFile( m_file, &odmlList, sizeof(odmlList), &writeSize, NULL );
   }

   // dmlh
   {
    AVIEXTHEADER dmlh = {0};
    dmlh.fcc = mmioFOURCC('d','m','l','h');
    dmlh.cb = sizeof(AVIEXTHEADER) - 8;
    dmlh.dwGrandFrames = m_totalFrame;

    DWORD writeSize;
    WriteFile( m_file, &dmlh, sizeof(dmlh), &writeSize, NULL );
   }


   // JUNK
   {
    // ファイル作成時にすっとばしたヘッダ分の領域
    const DWORD preFileHeaderSkipSize =
     sizeof(LIST)              //< RIFF-AVI LIST
      + sizeof(LIST)             //< hdrl List
       + sizeof(CHUNK) + sizeof(MainAVIHeader)      //< avih Chunk + MainAVIHeader
       + sizeof(LIST)            //< list-strl
        + sizeof(CHUNK) + sizeof(AVIStreamHeader)    //< strh Chunk + AVIStreamHeader
        + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)    //< strf Chunk + BITMAPINFOHEADER
        + sizeof(AVISUPERINDEX) + SUPER_INDEX_ENTRY_SIZE  //< AVISUPERINDEX + SUPER_INDEX_ENTRY_SIZE
       + sizeof(LIST)            //< odml List
        + sizeof(AVIEXTHEADER)         //< AVIEXTHEADER
      + sizeof(LIST);            //< movi LIST
    DWORD fileHeaderSkipSize = preFileHeaderSkipSize;


    if ( PADDING_GRANULARITY )
    {
     const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
     fileHeaderSkipSize = preFileHeaderSkipSize + (PADDING_GRANULARITY - (preFileHeaderSkipSize % PaddingGranularity));
    }


    // 'movi'はすでに記述されているのでその分の領域は計算に入れない
    fileHeaderSkipSize -= sizeof(LIST);

    // 実際に記述したヘッダ分
    const DWORD writtenHeaderSize =
     sizeof(LIST)                       // riff-avi
      + sizeof(LIST)                      // list-hdrl
       + sizeof(CHUNK) + sizeof(MainAVIHeader)               // avih + MainAVIHeader
       + sizeof(LIST)                     // list-strl
        + sizeof(CHUNK) + sizeof(AVIStreamHeader)             // strh + AVIStreamHeader
        + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)             // strf + BITMAPINFOHEADER
        + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size()  // AVISUPERINDEX + AVISUPERINDEX_ENTRY
       + sizeof(LIST)                     // LIST-odml
        + sizeof(AVIEXTHEADER);                  // AVIEXTHEADER
    const DWORD junkSize = fileHeaderSkipSize - writtenHeaderSize;
    writeJUNK( junkSize );
   }
  }

 }


 // 指定サイズのJUNKチャンクを書き込む。CHUNK込み
 void writeJUNK( DWORD junkSize )
 {
  CHUNK chunk;
  chunk.dwFourCC = mmioFOURCC('J','U','N','K');
  chunk.dwSize = junkSize - sizeof(chunk);

  DWORD writeSize;
  WriteFile( m_file, &chunk, sizeof(chunk), &writeSize, NULL );
  //SetFilePointer( m_file, chunk.dwSize, NULL, FILE_CURRENT );
  std::vector< BYTE > blankData( chunk.dwSize );
  WriteFile( m_file, &blankData[0], chunk.dwSize, &writeSize, NULL );
 }

 // 2048境界になるようJUNKを書き込む。
 DWORD writePaddingJUNK()
 {
  // 2048バイト境界になるようJUNKをつめる
  if ( PADDING_GRANULARITY )
  {
   DWORD padding = SetFilePointer( m_file, 0, NULL, FILE_CURRENT );
   
   const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
   const DWORD paddingOverSize = padding % PaddingGranularity;
   if ( paddingOverSize )
   {
    // つめるjunkサイズを計算する。
    // chunk構造体より大きいサイズでないとならないので、それより小さければかさ増しする
    // PADDING_GRANULARITYがsizeof(CHUNK)より小さいと問題が出るかも
    const DWORD junkSize = PADDING_GRANULARITY - paddingOverSize;
    if ( junkSize > sizeof(CHUNK) )
    {
     writeJUNK( junkSize );
     return junkSize;
    }
    else
    {
     writeJUNK( junkSize + PADDING_GRANULARITY );
     return junkSize + PADDING_GRANULARITY;
    }
   }
  }

  return 0;
 }




 HANDLE m_file;
 DWORD m_nowRiffFrame;
 DWORD m_totalFrame;
 
 DWORD m_numOfRiff;
 DWORD m_nowRiffSize;
 DWORD m_mostLargeImageSize;

 DWORD m_maxNumOfRiff;
 std::vector< AVIINDEXENTRY > m_aviIndexEntryList;
 std::vector< AVISUPERINDEX_ENTRY > m_superIndexEntryList;
 
 DWORD m_moviChunkOffset;   //< 'movi'チャンク位置からの距離
 std::vector< AVISTDINDEX_ENTRY > m_aviStdIndexEntryList;

 LARGE_INTEGER m_nowRiffFileTopPos; //< 各チャンクサイズを書き換えに戻る際に使用する、チャンク開始位置
 LARGE_INTEGER m_moviListPos;  //< 'movi'開始位置
 
 bool m_isAVIX;

 DWORD m_width, m_height;
 DWORD m_imageSize;
 DWORD m_fps;
 DWORD m_riffAVIChunkFrame;   //< AVI-RIFF 内のフレーム数
 DWORD m_riffAVIChunkSize;   //< AVI-RIFFのサイズ
};






struct ObjPos
{
 float x, y;
 float vx, vy;
};

 
 
LRESULT CALLBACK wndProc(
 HWND hWnd,
 UINT msg,
 WPARAM wParam,
 LPARAM lParam )
{
 static DIB32 back, image;
 static ObjPos pos[ 64 ];
 static AVIRecorder avi;

 switch (msg)
 {
 case WM_DESTROY:
  //SendMessage( hWnd, WM_TIMER, 0, 0 );
  avi.close();
  ShowWindow( hWnd, SW_HIDE );
  PostQuitMessage(0);
  break;
 case WM_CREATE:
  SetTimer( hWnd, TIMER_ID, 1000/FPS, NULL );

  back.create( WINDOW_WIDTH, WINDOW_HEIGHT );
  image.create( TEXT("image.bmp") );

  // init obj
  {
   srand( GetTickCount() );

   const int n  = sizeof(pos) / sizeof(*pos);
   for (int i=0; i<n; ++i)
   {
    pos[i].x = static_cast<float>( std::rand() % WINDOW_WIDTH );
    pos[i].y = static_cast<float>( std::rand() % WINDOW_HEIGHT );
    pos[i].vx = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 8 - 4;
    pos[i].vy = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 8 - 4;
   }
  }
  
  if ( !avi.create( TEXT("output.avi"), back.getWidth(), back.getHeight(), FPS ) )
  {
   MessageBox( hWnd, TEXT("errir create avi"), NULL, MB_OK );
  }

  break;
 case WM_PAINT:
  {
   PAINTSTRUCT ps = {0};
   HDC hdc = BeginPaint( hWnd, &ps );
   back.render( hdc, hWnd );
   EndPaint( hWnd, &ps );
  }
  break;
 case WM_TIMER:
  ZeroMemory( back.getPixelAddr(), back.getWidth() * back.getHeight() * 4 );
  
  // move obj + render
  {
   const int n  = sizeof(pos) / sizeof(*pos);
   for (int i=0; i<n; ++i)
   {
    if ( pos[i].x + pos[i].vx < 0 || pos[i].x + pos[i].vx > WINDOW_WIDTH ) pos[i].vx = -pos[i].vx;
    if ( pos[i].y + pos[i].vy < 0 || pos[i].y + pos[i].vy > WINDOW_HEIGHT ) pos[i].vy = -pos[i].vy;
    pos[i].x += pos[i].vx;
    pos[i].y += pos[i].vy;

    image.render< DIB32::PutColorAdd >( back, pos[i].x, pos[i].y, image.getWidth(),image.getHeight(), 0,0,image.getWidth(),image.getHeight() );
   }
  }

  InvalidateRect( hWnd, NULL, FALSE );
  avi.update( back );


  // title
  {
   TCHAR title[ 256 ] = {0};
   wsprintf( title, TEXT("%d(%d)"), avi.getNowRiffFrame(), avi.getTotalFrame() );
   SetWindowText( hWnd, title );
  }
  break;

 default:
  return DefWindowProc( hWnd, msg, wParam, lParam );
 }
 
 return 0;
}
 



 
 
 
int WINAPI WinMain(
 HINSTANCE hInstance,
 HINSTANCE, PSTR, int )
{
 LPCTSTR WINDOW_NAME = TEXT("sample");
 
 WNDCLASSEX wc;
 wc.style  = CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc = reinterpret_cast<WNDPROC>( wndProc );
 wc.cbClsExtra = 0;
 wc.cbWndExtra = 0;
 wc.cbSize  = sizeof( WNDCLASSEX );
 wc.hInstance = hInstance;
 wc.hIcon  = NULL;
 wc.hIconSm  = NULL;
 wc.hCursor  = LoadCursor( NULL, IDC_ARROW );
 wc.hbrBackground= reinterpret_cast<HBRUSH>( GetStockObject(WHITE_BRUSH) );
 wc.lpszMenuName = NULL;
 wc.lpszClassName= WINDOW_NAME;
 if ( !RegisterClassEx(&wc) ) return 0;
 
 LONG winWidth = WINDOW_WIDTH
  + GetSystemMetrics(SM_CXEDGE)
  + GetSystemMetrics(SM_CXBORDER)
  + GetSystemMetrics(SM_CXDLGFRAME);
 LONG winHeight = WINDOW_HEIGHT
  + GetSystemMetrics(SM_CYEDGE)
  + GetSystemMetrics(SM_CYBORDER)
  + GetSystemMetrics(SM_CYDLGFRAME)
  + GetSystemMetrics(SM_CYCAPTION);
 HWND hWnd = CreateWindowEx(
  0, WINDOW_NAME, NULL, WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME,
  CW_USEDEFAULT, CW_USEDEFAULT, winWidth, winHeight,
  NULL, NULL, hInstance, NULL);
 if ( !hWnd ) return -1;
 
 ShowWindow( hWnd, SW_SHOWNORMAL );
 UpdateWindow( hWnd );

 
 MSG msg;
 for (;;)
 {
  if ( !GetMessage(&msg, NULL, 0, 0) ) break;
  TranslateMessage( &msg );
  DispatchMessage( &msg );
 }
 
 
 UnregisterClass( WINDOW_NAME, hInstance );
 return msg.wParam;
}

2016年5月29日日曜日

ActiveWindowLogger Log2CSV ver 1.01




■ダウンロード

File: AWL_Log2CSV_v101.zip
Size: 69.2KB
分類: フリーソフト




■概要
    ActiveWindowLoggerのログファイル(.bin2)をCSV形式に変換して指定フォルダに保存します



■動作環境
    Windows XP, 7
    CPU 800MHz以上
    メモリ 256MB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へどうぞ。



■使い方
    ログファイル(.bin2)をウィンドウにD&Dしてください。
    設定してある出力先フォルダにcsvに変換されて出力されます。

    フォルダ1個のみをD&Dした際に表示されるメニューから「フォルダ内のログを変換」を選ぶと、
    フォルダ直下にあるログファイルのみが指定出力先にCSVに変換されて出力されます。

    実行ファイルに直接D&Dするとあらかじめ設定された出力先に変換されて出力されます。


    *出力先フォルダを設定する都合上、
    複数フォルダおよびサブフォルダ以下のファイルはファイル名がぶつかるため走査しません。

    *ActiveWindowRecorderのログファイル(.bin)も変換できます。


    ●マルチスレッド数の設定
        複数コアCPUをご使用の方はスレッド数の値を増やすと変換速度が上がるかもしれません。

    ●出力先の変更
        CSVが出力されるフォルダを設定します。
        フォルダ1個のみをD&Dをした際に表示されるメニューから「出力先フォルダに設定」を選ぶと、
        ドロップされたフォルダを出力先に設定できます。


    ●出力文字設定
            出力される文字列の設定ができます。
            テストボタンで押すと、指定文字が置き換えられたあとの文字列がテストのところに表示されます。
            指定文字以外はそのまま出力されます。
            指定文字は以下のものが使えます
                「%h」時
                「%m」分
                「%s」秒
                「%l」ミリ秒
                「%H」時(2桁)
                「%M」分(2桁)
                「%S」秒(2桁)
                「%L」ミリ秒(3桁)
                「%i」ミリ秒(時刻*60*60*1000 + 分*60*1000 + 秒*1000 + ミリ秒。時刻を一項目で表現します)
                「%n」表示時間 分
                「%e」表示時間 秒
                「%c」表示時間 ミリ秒
                「%E」表示時間 秒(2桁)
                「%C」表示時間 ミリ秒(3桁)
                「%d」表示時間 ミリ秒(分*60*1000 + 秒*1000 + ミリ秒。表示時間を一項目で表現します)
                「%t」ウィンドウタイトル
                「%x」実行ファイルパス
                「%%」文字の%

    ●ファイルにBOMをつける
        ファイル先頭にBOMを記述します
    ●改行コード
        改行コードをLFかCRLFのどちらかを使用するようにします



■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。

    zlibライブラリを使用しています。
        Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler
        http://www.winimage.com/zLibDll/



■更新履歴
    2016-05-29 ver1.01
        出力文字をいじくれるよう修正
        BOMの有無を選択できるよう修正
        改行コードを選択できるよう修正
    2013-02-10 ver1.00
        作成

2016年5月28日土曜日

ActiveWindowLogger ver2.05




■ダウンロード

File: ActiveWindowLogger205zip
Size: 136KB
分類: フリーソフト




■概要
    PCで一日何をしていたかログを取るソフトです。
    この日のこの時間に何をしていたか、
    あるいは一日でアプリケーションを何時間使用したかが一目でわかります。



■動作環境
    Windows XP, 7
    CPU 800MHz以上
    メモリ 256MB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へどうぞ。



■使い方
    起動している間ログをとります。
    最小化するとタスクトレイのみの表示に。
    ログはプログラム終了時と日付が変わった直後にlogフォルダに保存されます。

    記録されるのは
        時刻(time)
        表示時間(duration)
        操作中のウィンドウタイトル(title)
        操作中のプログラムのパス(exe)
    の4つのみです。



    logボタン
        ログデータファイル(.bin2)を選択し、読み込んで表示します
        ウィンドウにファイルをD&Dでも可
        またActiveWindowRecorderのログ(.bin)の読み込むことができます。
    prevボタン
        現在表示中のログより前のログがあれば表示します
        マウスの戻るボタンでも同様
    nextボタン
        現在表示中のログより後のログがあれば表示します
        マウスの進むボタンでも同様
    todayボタン
        今日の最新のログを表示します

    タイムラインイメージ
        大雑把に使用時間をプログラム別に着色して表示しています。
        上下に分かれており、上が24時間表示、下がカーソル位置付近の1時間のデータになります。



    txtで保存
        現在表示中のログをテキスト形式でファイルに出力します
        各行に「[時刻(HH:mm:ss.SSS)](アクティブ時間(HH:mm:ss.SSS))"ウィンドウタイトル" - "実行ファイルパス"」で記録されます
    csvで保存
        現在表示中のログをCSV形式でファイルに出力します
        各行に「時刻(HH:mm:ss.SSS), アクティブ時間(ミリ秒), "ウィンドウタイトル", "実行ファイルパス"」で記録されます
    アプリケーション使用率
        表示中のログから各アプリケーションの使用時間などを表示します
            比率 (rate)
            合計アクティブ時間 (duration)
            平均使用時間 (ave)
            実行ファイルパス (exe)
            表示回数 (num)
    基本設定
        アクティブウィンドウチェック周期
            アクティブが切り替わっていないかチェックする周期の設定で、
            数値が小さいほど細かくチェックしますが負担が大きくなります
        タイムラインイメージ更新周期
            タイムラインイメージ上のカーソル位置をチェック、表示更新する周期を変更できます。
            タイムラインイメージ上でのリストの選択速度に影響が出ます。
            数値が小さいほど細かくチェックしますが負担が大きくなります
        オートセーブ
            ログを一定タイミングで自動保存するようにします。予期せぬブルースクリーン対策などに。
            「しない」であれば0時に、
            「一時間おき」であれば時刻が変わるごとに、
            「半日おき」であれば0時と12時に、ログを保存します
        古いログファイルを自動削除
            0時に古いログがないかチェックし、あれば削除します
        終了を確認する
            終了時に確認ウィンドウを出します

        アクティブウィンドウリストCSVを自動出力する
            オートセーブ設定のタイミングとアプリケーション終了時に、指定ディレクトリにCSVファイルを出力します。
            このCSVファイルはオートデリートで削除されません。
        アクティブウィンドウリストテキストを自動出力する
            オートセーブ設定のタイミングとアプリケーション終了時に、指定ディレクトリにテキストファイルを出力します。
            このテキストファイルはオートデリートで削除されません。
        出力ファイル名
            自動出力されるCSVファイル名を設定できます。
            ファイル名には「\」「/」「?」「*」「"」「>」「<」「|」の文字は設定できません。
            また、以下の指定文字を使用すると、自動的に数値に置き換えられます。
            指定文字以外はそのまま使用されます。
                 「%y」年4桁
                 「%m」月
                 「%d」日
                 「%h」時
                 「%i」分
                 「%s」秒
                 「%l」ミリ秒
                 「%Y」年(下2桁。2015年であれば15と出力されます)
                 「%M」月(2桁。1月であれば01と出力されます)
                 「%D」日(2桁)
                 「%H」時(2桁)
                 「%I」分(2桁)
                 「%S」秒(2桁)
                 「%L」ミリ秒(3桁)
                 「%%」文字の%
            例
                %Y%M%d → 160518
                [%y%m%d] → [2016518]
                [%Y%M%d] → [160518]
                (%Y-%M-%d) → (16-05-18)
            拡張子は保存するファイル種類に応じて「.csv」か「.txt」が追記されます
            

        アプリケーション利用率CSVを自動出力する
            オートセーブ設定のタイミングとアプリケーション終了時に、指定ディレクトリにCSVファイルを出力します。
            CSVファイルは各行に「使用率(%),アクティブ時間(ミリ秒),実行ファイルパス」で記録されます
        アプリケーション利用率テキストを自動出力する
            オートセーブ設定のタイミングとアプリケーション終了時に、指定ディレクトリにテキストファイルを出力します。
            テキストファイルは各行に「[使用率(%)], アクティブ時間(HH:mm:ss.SSS) (平均使用時間(HH:mm:ss.SSS)) - "実行ファイルパス" [アクティブ回数]」で記録されます



        出力文字設定
            テキストコピー、CSVコピー、テキスト保存、CSV保存のときに使用される文字列の設定ができます。
            テストボタンで押すと、指定文字が置き換えられたあとの文字列がテストのところに表示されます。
            指定文字以外はそのまま使用されます。
            [アクティブウィンドウリスト]
                指定文字は以下のものが使えます
                「%h」時
                「%m」分
                「%s」秒
                「%l」ミリ秒
                「%H」時(2桁)
                「%M」分(2桁)
                「%S」秒(2桁)
                「%L」ミリ秒(3桁)
                「%i」ミリ秒(時刻*60*60*1000 + 分*60*1000 + 秒*1000 + ミリ秒。時刻を一項目で表現します)
                「%n」表示時間 分
                「%e」表示時間 秒
                「%c」表示時間 ミリ秒
                「%E」表示時間 秒(2桁)
                「%C」表示時間 ミリ秒(3桁)
                「%d」表示時間 ミリ秒(分*60*1000 + 秒*1000 + ミリ秒。表示時間を一項目で表現します)
                「%t」ウィンドウタイトル
                「%x」実行ファイルパス
                「%%」文字の%
            
            [アプリケーション使用率]
                指定文字は以下のものが使えます
                「%r」使用率(パーセント)
                「%h」合計時
                「%m」合計分
                「%s」合計秒
                「%l」合計ミリ秒
                「%H」合計時(2桁)
                「%M」合計分(2桁)
                「%S」合計秒(2桁)
                「%L」合計ミリ秒(3桁)
                「%d」合計ミリ秒(時刻*60*60*1000 + 分*60*1000 + 秒*1000 + ミリ秒。時刻を一項目で表現します)
                「%o」平均時間 時
                「%n」平均時間 分
                「%e」平均時間 秒
                「%c」平均時間 ミリ秒
                「%N」平均時間 分(2桁)
                「%E」平均時間 秒(2桁)
                「%C」平均時間 ミリ秒(3桁)
                「%a」平均時間 ミリ秒(時*60*60*1000 + 分*60*1000 + 秒*1000 + ミリ秒。平均時間を一項目で表現します)
                「%d」表示時間 ミリ秒(分*60*1000 + 秒*1000 + ミリ秒。表示時間を一項目で表現します)
                「%x」実行ファイルパス
                「%u」表示回数
                「%%」文字の%



    色
        リストの色分けに関する設定です。ダブルクリックで色を変更、
        右クリックで項目の追加や削除が可能です

        アプリケーション別
            アプリケーションごとに別々の色をつけます
        汎用
            アプリケーションごとに適当に色を振り分けます
        時間別
            1時間ごとに色を変更します
        なし

        save, load
            色の設定のみをファイル(.color)に保存できます。
            D&Dでも読み込み可能
        clear
            色設定を初期状態に戻します



■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。

    zlibライブラリを使用しています。
        Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler
        http://www.winimage.com/zLibDll/



■更新履歴
    2016-05-28 ver2.05
        テキストコピー、テキストで保存、CSVコピー、CSV保存での書式を多少変更できるよう修正
        CSV自動保存のファイル名を任意のものにできるよう修正
        テキストを自動保存可能なよう修正
        アプリケーション使用率ウィンドウにファイル出力ボタンを追加
        アプリ使用率を自動出力する機能を追加
        ファイル出力時にBOMの有無を選択できるよう修正
        改行コードを選択できるよう修正
    2015-01-22 ver2.04a
        エクスプローラが再起動した際にタスクトレイアイコンを再設定するよう修正
    2014-09-13 ver2.04
        終了確認をするよう変更
    2013-03-03 ver2.03b
        タイムライン上での範囲選択の動作仕様を変更
    2013-02-03 ver2.03a
        日付変更時にアクティブなウィンドウ情報が記録されないのを修正
    2012-09-21 ver2.03
        オートセーブ時にCSVファイルを指定ディレクトリに出力できる機能を追加
        メニューの「TXT形式で保存」「CSV形式で保存」のときにデフォルトのファイル名が表示されていないのを修正
        CSV出力はリストのソート状態の影響を受けないように修正
        ver2.02系でのconfigファイル読み込みの下位互換がうまくいっていなかったのを修正
        色表示が「RRGGBB」ではなく「BBGGRR」になっていたのを修正
        コンフィグウィンドウで色を変更した瞬間にリストの表示が更新されないのを修正
    2012-08-03 ver2.02a
        オートセーブに「3時間おき」「6時間おき」を追加
        ウィンドウ左ダブルクリックで「アプリケーション使用率」ウィンドウを表示するよう修正
        ウィンドウ右ダブルクリックでコンフィグウィンドウを表示するよう修正
    2012-07-10 ver2.02
        検索機能の追加
        リスト項目クリックにタイムラインイメージの下側を更新するよう修正
    2012-07-05
        アプリケーション使用率ウィンドウのリスト色別表示するよう修正
        アプリケーション使用率ウィンドウでコピー可能なよう修正
        アプリケーション使用率ウィンドウから前日後日データの読み込みを可能なよう修正
    2012-07-03 ver2.01
        ツールチップを表示するよう修正
    2012-07-01
        自動削除機能追加
    2012-06-30
        タイムラインの下側が00:30未満のとき描画できていなかったのを修正
        64bitWindowsだとプログラムのパスが正常に取得できないのを修正
    2012-06-27
        タイムラインをドラッグ後正常に描画できていないのを修正
    2012-06-25
        アプリケーション使用率のrateが%表記ではなかったのを修正
    2012-06-24 ver2.00

2016年4月28日木曜日

Shiftキーでウィンドウの内側 ver1.00






File: uchigawa_v100.zip
Size: 34.43KB
分類: フリーソフト




■概要
    シフトキーを押している間、カーソルがウィンドウ外に出なくなります。



■想定した使い所
     エクスプローラやブラウザなどのスクロールバーは、シフトキーを押しながらクリックすることで、
    クリックした位置にバーを瞬時に移動することができます。
     マウスカーソルをスクロールバー上に移動させる際、そのまま行き過ぎることが多々あったので
    カーソル移動範囲を制限して、スクロールバー上にカーソルを置きやすくしようという趣旨のソフトです。



■動作環境
    Windows 8, 10
    CPU 2GHz以上
    メモリ 2GB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へ。



■使い方
    ●有効/無効
        チェックを外すとカーソルの範囲制限を行わなくなります。一時的にオフにしたい場合などに。
    ●キー設定
        ・動作キー
            シフトキー使用を想定していますが、トグルモードを使う場合はキー設定を変更したほうが便利かもしれません
        ・トグルモード
            設定キーを押すとカーソル移動範囲を制限、もう一度押すと解除される操作方法が変わります
    ●範囲限定するウィンドウの選択
        ・カーソル下のウィンドウ
            シフトキーを押したときにカーソル下にあるウィンドウ上に範囲制限するようにします
        ・現在アクティブなウィンドウ
            シフトキーを押したときにアクティブなウィンドウ上に範囲制限するようにします
    ●範囲
        ・ウィンドウサイズ
            制限する範囲をウィンドウサイズの大きさにします
        ・クライアント領域
            制限する範囲をウィンドウのクライアント領域(タイトルバーやフレームを除いたウィンドウのメインの領域)
            の大きさにします
        ・クライアント領域+タイトルバー高さ
            制限する範囲をウィンドウのクライアント領域にタイトルバー領域を加えたものの大きさにします
        ・範囲の縮小拡大
            範囲を少し大きくしたり小さくしたりします。マイナスなら小さく、プラスなら大きくなります


■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。



■更新履歴
    2016-04-26 ver1.00

カーソル画面端で実行 ver1.00






file: hajikko_v100.zip
size: 66.04KB
分類: フリーソフト




■概要
    カーソルを画面端上下、あるいは左右に往復させると、設定したファイルを実行します。
    マルチモニタ対応


■動作環境
    Windows 8, 10
    CPU 2GHz以上
    メモリ 2GB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へ。



■使い方
    カーソルを画面の左右端か、上下端を往復することでファイルを実行することができます。
    
    例:
    回数が3の場合、カーソルを画面上→画面下→画面上と、端に3回触れることで実行されます
    同じく回数3で、カーソルを画面右→画面左→画面右と、端に3回触れることで実行されます
    触れた画面端とは反対側の画面端に触れないとカウントされません
    ので、画面右→画面下と触れても回数1として扱われます。



    ●有効/無効
        チェックを外すとカーソル位置チェックを行わなくなります。一時的にオフにしたい場合などに
    ●画面座標再計算
        モニタの解像度や位置設定を変更したら、このボタンを押してください
        本アプリケーションは起動時に、モニタ情報から当たり判定を事前に計算しています。
        そのため起動後に画面情報が変更されると当たり判定データとのずれが生じます。
        ボタンを押すことで当たり判定が再計算されます。
        
    ●設定
        ・端範囲(px)
            カーソルが画面端から指定px内にあれば、端にあると認識します
        ・回数
            カーソルが画面端に指定回数触れたら、設定ファイルを実行するようにします
        ・有効時間(ms)
            カーソルが画面端から端に触れるまでの猶予時間の設定で、
            設定した時間内に、端から端に触れたら、連続で触れたと認識します
        ・カーソルチェック周期
            カーソルの位置を取得する周期を設定します
            多いほど細かくカーソル位置をチェックするので、
            端に触れている時間が短くても触れたと認識しやすいですが、処理負担がかかります。
            少ないと、端に触れたにもかかわらず触れたと認識しないことがありますが、処理負担は小さいです
            有効時間や端範囲の設定を甘くすることで少ない周期設定でも実用に耐えるかもしれません
        ・実行時に音を鳴らす
            実行処理を行う際に音が鳴ります
    ●実行パス
        画面端に指定回数触れたときに実行されるファイルを設定します。
        変更ボタンでファイル選択ウィンドウが表示されるので実行したいファイルを選んでください
        ウィンドウにファイルをD&Dすることでも設定できます。
    ●テスト
        設定したファイルがちゃんと実行可能かテストできます
    


■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。



■更新履歴
    2016-04-26 ver1.00

2016年1月21日木曜日

clickEffect ver1.0




file: clickEffect100.zip
size: 43.53KB
分類:フリーソフト




■概要
    クリックした位置にしばらく表示物を設置します。
    正しい位置をクリック出来ていたかちょっと知りたいときにどうぞ。



■動作環境
    Windows XP, 8, 10
    CPU 2GHz以上
    メモリ 2GB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へ。



■使い方
    起動中、クリックした位置に輪っか表示がでます。

    ●表示時間
     指定した時間表示されます。単位ミリ秒
    ●サイズ
     表示物の最終サイズを設定できます。
    ●アニメタイプA,アニメタイプB
     輪っかアニメーションの種類を選択できます。
    ●右クリックエフェクト色、左クリックエフェクト色
     表示物の色を色変更ボタンで変更することができます。
    ●十字を描画する
     表示物に十字を描画してクリック位置を解かりやすくします。



■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。



■更新履歴
    2016-01-21 ver 1.0

2016年1月20日水曜日

toClipboard ver1.00



file: toClipboard100.zip
size: 45.7KB
分類: フリーソフト





■概要
    ファイル内容をそのままクリップボードに転送します



■動作環境
    Windows XP, 8, 10
    CPU 2GHz以上
    メモリ 2GB以上



■アンインストール
    レジストリは触っておりません。
    削除の際はフォルダごとゴミ箱へ。



■使い方
    実行ファイルアイコンにD&Dすることで、ファイル内容をそのままクリップボードに転送できます。
    コンフィグ設定は普通に起動することで行います。
    起動後にウィンドウにD&Dすることでもクリップボードへ転送することができます。


    ●拡張子別のクリップボードデータ形式の設定
    ○拡張子
        「.txt」や「.wav」などの拡張子文字列を入力してください。「.」(ドット)は必須です。
    ○データ形式
        クリップボードへデータを送る際の形式を選択します
        ・データ形式(標準)
            CF_TEXTやCF_WAVEといったWindows標準の形式で転送する場合、このコンボボックスから選択します
        ・データ形式(数値)
            データ形式の数値が分かっている場合は、数値を入力してください
        ・データ形式(登録)
            データ形式が文字列で指定します。設定した文字列でRegisterClipboardFormat()を行いクリップボードへ送ることになります。
    ○登録ボタン
        拡張子とデータ形式の設定が終わったら、登録ボタンを押して登録してください。
        D&Dされたファイルの拡張子を見て、指定された形式でクリップボードを転送するようになります。
        D&Dされたファイルの拡張子が登録されていなかった場合、クリップボードへは転送されません。

    ●オプション
    ○ファイルサイズが大きい場合確認する。
        指定サイズ以上のファイルがD&Dされた場合、クリップボードへ転送するか確認します
    ○複数ファイルだったらファイルコピーにする
        複数ファイルがD&Dされたら、ファイルコピーとしてクリップボードへ登録します
    ○テキストファイルがUNICODEか調べる
        D&Dされたファイル拡張子が「.txt」だったら、
        エンコードがUNICODEだったらCF_UNICODETEXTで、
        そうでなければCF_TEXTでファイル内容をクリップボードへ転送するようになります。



■そのほか
    このソフトウェアを使用し、何らかの障害が発生しても責任を取りかねますのでご了承ください。



■更新履歴
    2016-01-20 ver1.0