任意解像度のビットマップにミリ単位で描画する方法

ビットマップファイルを動的に生成する場合、解像度情報はデフォルトの96dpiで生成される。
デバイスに依存しないで、任意解像度のビットマップにMM_HIMETRIC(1/100mm)単位で描画する場合、以下の手順で、ビューポート範囲等を設定する。

void SaveImage(HDC hDC, long Width, long Height, int dpi)
{
  ::SaveDC(hDC);

  HPEN hPen = ::CreatePen(PS_SOLID|PS_GEOMETRIC, 10, RGB(0,0,0));
  HBRUSH hBrush = ::CreateSolidBrush(RGB(0xff, 0xff, 0xff));

  ::SelectObject(hDC, hPen);
  
  RECT rr = {0, 0, Width, Height};
  ::FillRect(hDC, &rr, hBrush);


  const double INCH_TO_MM = 25.4;   // 1inch = 25.4mm
  double logicalWidth  = (double)Width / dpi * INCH_TO_MM;
  double logicalHeight = (double)Height / dpi * INCH_TO_MM;  

  // MM_HIMETRIC相当の論理幅
  long lw = (long)(logicalWidth * 100.0 + 0.5);
  long lh = (long)(logicalHeight * 100.0 + 0.5);

  SIZE LastSize;
  SIZE Size;
  POINT LastPoint;

  ::SetMapMode(hDC, MM_ANISOTROPIC); // 論理単位を任意に
  ::SetWindowExtEx(hDC, lw, -lh, &LastSize); // DCの論理幅・方向をmm単位で上方向を正とする。
  ::SetViewportExtEx(hDC, Width, Height, &LastSize); // DCの物理幅は設定サイズ

  // 描画原点をDCの中心とする。原点を左下にしたい場合は、abs(Size.cy / 2)をabs(Size.cy)に
  ::GetViewportExtEx(hDC, &Size);
  ::SetViewportOrgEx(hDC, abs(Size.cx / 2), abs(Size.cy / 2), &LastPoint);


  // 一本線を書いてみる
  POINT pt;
  ::MoveToEx(hDC, 0, 0, &pt);
  ::LineTo(hDC, 5000, -5000);

  // DCを元に戻す
  ::RestoreDC(hDC, -1);

  // 後始末
  ::DeleteObject(hPen);
  ::DeleteObject(hBrush);
}

あとは、保存先のTPictureにTCanvas::CopyRectやBitBltなりで転送して、ファイルを保存。 ATL::CImageで生成したオブジェクトのhDCに渡すのもOK。 こしらえたビットマップファイルの解像度情報は96dpi(=画面)になっているので、最後に解像度情報をいじってやる。

  // ビットマップファイルの解像度情報を書き換え
  FILE* fp;
  fopen_s(&fp, FileName, "r+b");
  fseek(fp, 38, SEEK_SET);
  DWORD32 dpm;
  dpm = (int)(dpi * 1000.0 / INCH_TO_MM);
  fwrite((void*)&dpm, sizeof(dpm), 1, fp);
  fwrite((void*)&dpm, sizeof(dpm), 1, fp);

  fclose(fp);

追記:
しかし、C++Builderじゃないけど、ATL::CImageはかなり便利。MFCとの混在も当然可能だし、ファイルの保存とかも一発でやってくれる。昔、ファイルに保存するとき、ビットマップヘッダとかパレット情報とか色々生成したな・・・。(遠い目)