[BMP]ビットマップの保存
ビットマップの保存
今回は簡単です。
DIBのビットマップを以下の順にファイルに書いてやればOK。
BITMAPFILEHEADER構造体
↓
BITMAPINFOHEADER構造体
↓
カラービットデータ
回転のコード に適当にボタンを追加して、ON_BN_CLICKED のハンドラを追加します。
void CSampleDlg::OnBnClickedButton1()
{
// ファイルを保存する形式のダイアログ
CFileDialog dlg(FALSE);
// BITMAPFILEHEADER作成
// BITMAPINFOHEADERを作成していない場合は作る。
// BITMAPINFOHEADER構造体についてはこちら
BITMAPFILEHEADER bfh = {0};
bfh.bfType = 0x4D42;
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfSize = bfh.bfOffBits + bih.biSizeImage;
// ダイアログ起動
if (dlg.DoModal() != IDOK) return;
// 保存先のファイルパス取得
CString filename = dlg.GetPathName();
// ファイルオープン
CFile file;
if (!(file.Open(filename, CFile::modeCreate | CFile::modeReadWrite))) return;
// 書き込み処理(一応例外をtry-catchする)
try
{
file.Write(&bfh, sizeof(BITMAPFILEHEADER));
file.Write(&bih, sizeof(BITMAPINFOHEADER));
file.Write(pDstData, m_DstBih.biSizeImage);
}
catch(CFileException *)
{
AfxMessageBox(_T("書き込み失敗"));
file.Close();
return;
}
file.Close();
}
135度回転した画像を保存した結果。
元画像が288*216ピクセル = 186,678 バイトであるのに対し、
回転後画像は356*356ピクセル = 380,262 バイト。
圧縮なしなので、計算どおりのサイズとなっています。
→ bfh.bfSize = bfh.bfOffbits + bih.biSizeImage
[BMP]バイリニア法(線形補間)
コードとしては美しくないが、なんとかバイリニア法を実現させた。
理論は簡単だけど、実際コードにすると意外と参考にできそうなのが
みつからなかったり。
縮小された時点でも結構わかるけど、リンク開いて拡大してみれば
尚わかる違い。
基本的なアルゴリズムは数学的要素を多分に含むが簡単に。
(アフィン変換とか行列とかが多分にでてきます。)
[バイリニア法]
画像を回転させ、回転後の座標から回転元の座標を求めることは
これまでにも数回行っている。
これまでは、上記方法で求めた座標をすぐにintキャストして
(つまり最も近い座標を使う、ニアレストネイバー法)いたが。
今回は小数点となっている座標と、それを囲む直近4座標との
距離から色の濃度を求めるというものである。
その求められた色をニアレストネイバー法で求められる座標に
当てはめる。
実現した際のコードは、前回 のコードのfor文をいじっただけですんだ。
(実際はもっと関数化など美しくすべきだけど、めんどかった。。)
for(int dstY = 0; dstY < dstHeight; dstY++)
{
for(int dstX = 0; dstX < dstWidth; dstX++)
{
// 求める座標①
double dsrcX = ((dstX - dtX) * dcos - (dstY - dtY) * dsin);
double dsrcY = ((dstX - dtX) * dsin + (dstY - dtY) * dcos);
// 求める座標から小数点を切り捨てた座標②
int isrcX = static_cast<int>(dsrcX);
int isrcY = static_cast<int>(dsrcY);
// ①と②の距離
double dx = dsrcX - isrcX;
double dy = dsrcY - isrcY;
// 元画像の座標
int srcX = static_cast<int>(dsrcX + 0.5);
int srcY = static_cast<int>(dsrcY + 0.5);
if((0 <= isrcX) && (isrcX < nSrcWidth) && (0 <= isrcY) && (isrcY < nSrcHeight))
{
// 直近4座標の色データを取得する
RGBTRIPLE rtLB, rtRB, rtLT, rtRT;
memcpy(&rtLB, &m_pSrcData[isrcX*3 + isrcY*srcLength],3);
memcpy(&rtRB, &m_pSrcData[(isrcX+1)*3 + isrcY*srcLength],3);
memcpy(&rtLT, &m_pSrcData[isrcX*3 + (isrcY+1)*srcLength],3);
memcpy(&rtRT, &m_pSrcData[(isrcX+1)*3 + (isrcY+1)*srcLength],3);
pDstData[dstX * 3 + dstY * dstLength] =
(BYTE)((1-dy) * ((1-dx) * rtLB.rgbtBlue + dx * rtRB.rgbtBlue) +
dy * ((1-dx) * rtLT.rgbtBlue + dx * rtRT.rgbtBlue) + 0.5);
pDstData[dstX * 3 + dstY * dstLength + 1] =
(BYTE)((1-dy) * ((1-dx) * rtLB.rgbtGreen + dx * rtRB.rgbtGreen) +
dy * ((1-dx) * rtLT.rgbtGreen + dx * rtRT.rgbtGreen) + 0.5);
pDstData[dstX * 3 + dstY * dstLength + 2] =
(BYTE)((1-dy) * ((1-dx) * rtLB.rgbtRed + dx * rtRB.rgbtRed) +
dy * ((1-dx) * rtLT.rgbtRed + dx * rtRT.rgbtRed) + 0.5);
}
}
}
ただし、上記コードは画面端(前回赤線引いたところ)にごみが入っており
完璧ではない。
どこかに条件文いれないといけないが、どうすればいいのかはまだ未検討。
- 詳解 画像処理プログラミング C言語で実装する画像処理アルゴリズムのすべて/昌達 慶仁
- ¥3,990
- Amazon.co.jp
[BMP]ビットマップの回転-修正版-
前回 の回転処理にはちょっとした不具合があったので、アルゴリズムから見直し。
(不具合内容は↑のリンク)
方針の変更として、画像中心を回転の中心とするのではなく、
回転したあとに平行移動して表示する方法に変更。
また、速度よりも精度を重視したアルゴリズムに変更。
回転のイメージ
わかりやすくするため、外周1ピクセルを赤線引っ張ってます。
外周が太くなっている部分は最近傍法というものが使われているため。
線形補間(バイリニア法)などを使えばもっと滑らかになるはず。まだ試せてない。
[回転部のみ修正]
void CSampleDlg::RotateBmp(int theta)
{
// deg→rad変換
double rad = (theta * M_PI / 180);
// 元画像の情報(何らかの方法で取得してください)
// バイト幅に関しては、「ビットマップのフォーマット
」参照
int nSrcWidth = ***; // ピクセル幅
int nSrcHeight = ***; // ピクセル高さ
int nSrcLength = ***; // バイト幅
BYTE *pSrcData = ***; // 元画像のカラーデータ
// sin、cos値の計算(関数内では定数として扱う)
double dsin = sin(rad);
double dcos = cos(rad);
// 各頂点の位置を格納
CPoint ptTop[4] = {CPoint(0, 0), // 左下
CPoint(nSrcWidth - 1, 0), // 右下
CPoint(0, nSrcHeight - 1), // 左上
CPoint(nSrcWidth - 1, nSrcHeight - 1)}; // 右上
// 回転後の各頂点の位置を計算し、回転後の頂点の中から
// 最小となる点を求める
double xMin = 0;
double xMax = 0;
double yMin = 0;
double yMax = 0;
for(int i = 1; i < 4; i++)
{
// 回転元→回転先への変換
double topX = ptTop[i].x * dcos + ptTop[i].y * dsin;
double topY = -ptTop[i].x * dsin + ptTop[i].y * dcos;
xMin = min(xMin, topX);
xMax = max(xMax, topX);
yMin = min(yMin, topY);
yMax = max(yMax, topY);
}
// 平行移動する移動量を求める
double dtX = -xMin;
double dtY = -yMin;
// 回転後の幅と高さを求める。
// (幅としての変換のため+1、四捨五入のため+0.5)
int nDstWidth = static_cast<int>(xMax - xMin + 1.5);
int nDtHeight = static_cast<int>(yMax - yMin + 1.5);
// バイト幅を求める
int nDstLength = 0;
if((nDstWidth*3)%4 == 0) nDstLength = nDstWidth*3;
else nDstLength = nDstWidth*3 + (4-(nDstWidth*3)%4);
// 回転後画像のカラーデータ確保
// 何もデータがない箇所は黒で表示するために0x00で初期化
BYTE *pDstData = new BYTE [nDstLength * nDtHeight];
memset(pDstData, 0x00, nDstLength * nDtHeight);
// 回転処理
for(int dstY = 0; dstY < nDtHeight; dstY++)
{
for(int dstX = 0; dstX < nDstWidth; dstX++)
{
// 回転先→回転元
int srcX = static_cast<int>((dstX - dtX) * dcos - (dstY - dtY) * dsin + 0.5);
int srcY = static_cast<int>((dstX - dtX) * dsin + (dstY - dtY) * dcos + 0.5);
if((0 <= srcX) && (srcX < nSrcWidth) && (0 <= srcY) && (srcY < nSrcHeight))
{
memcpy(&pDstData[dstX * 3 + dstY * nDstLength],
&pSrcData[srcX * 3 + srcY * nSrcLength],
3);
}
}
}
// 回転後データを表示するためにメンバ変数などへ
// コピーなどしてください。
delete [] pDstData;
}
[BMP]CreateDIBSection
CreateDIBSectionの使い方
HBITMAP CreateDIBSection(
HDC hdc // デバイスコンテキストのハンドル
CONST BITMAPINFO *pbmi // ビットマップデータ
UINT iUsage // データファイルのインジケータ
VOID **ppvBits // ビット値
HANDLE hSection // ファイルマッピングオブジェクトのハンドル
DWORD dwOffset // ビットマップのビット値へのオフセット
}
CreateDIBSection はWIN32APIとなります。
アプリケーションから直接書き込み可能なDIBを作成可能です。
当ブログで扱うビットマップは基本24ビットフルカラー以上なので、
第2引数のBITMAPINFOはBITMAPINFOHEADERをキャストします。
第3引数は「DIB_PAL_COLORS」と「DIB_RGB_COLORS」があります。
基本的にDIB_RGB_COLORSを使います。(前者は使い道わかりません)
第4引数はビットマップのイメージ部の先頭を指すポインタが得られます。
pbmi に設定されているbiWidthとbiHeightの値を元に、領域を動的に確保しています。
ここで確保された領域は、DeleteObjectにより破棄されます。
第5引数は基本NULLかと思います。値の入れ方はよくわかりません。
第6引数は、第5引数がNULLの時は無視されます。
[使用例]
簡単のため、全てOnPaint内に処理を記述します。
void CSampleDlg::OnPaint()
{
if (IsIconic())
{
・・・中略
}
else
{
CPaintDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
// BITMAPINFOHEADER の作成
BITMAPINFOHEADER bih = {0};
bih.biBitCount = 24; // 24ビットカラー
bih.biHeight = 256; // 高さ256
bih.biWidth = 256; // 幅 256
bih.biPlanes = 1; // 常に1
bih.biSize = sizeof(BITMAPINFOHEADER); // 構造体のサイズ
// カラーデータを受け取るポインタ
BYTE *pData;
// BITMAPINFOHEADER 情報からDIBを作成し、ハンドルを取得
HBITMAP hbmp = ::CreateDIBSection(dc.GetSafeHdc(),
(BITMAPINFO*)&bih,
DIB_RGB_COLORS,
(void**)&pData,
NULL,
0);
// 以降、pDataを操作することでカラーイメージを編集できます。
// 今回は黒→白のグラデーションを作成してみました。
for (int y = 0; y < bih.biHeight; y++)
{
for (int x = 0; x < bih.biWidth * 3; x += 3)
{
pData[x + y * bih.biWidth *3] = y;
pData[x + y * bih.biWidth *3 + 1] = y;
pData[x + y * bih.biWidth *3 + 2] = y;
}
}
// CreateDIBSectionで取得したハンドルをCBitmap オブジェクトに結びつける
CBitmap bmp;
bmp.Attach(hbmp);
CBitmap *old = dcMem.SelectObject(&bmp);
// 描画
dc.BitBlt(0, 0 ,bih.biWidth, bih.biHeight, &dcMem, 0, 0, SRCCOPY);
dcMem.SelectObject(old);
bmp.DeleteObject();
dcMem.DeleteDC();
}
[日記]MCA Platform合格
MCA(Microsoft Certified Associate)のPlatform
試験合格しました。
初めてベンダー試験(プロメトリック系)受けたけど、まぁ何とかなるもんだ。
626点/800点満点。
2週間電車での帰宅時間に参考書読んだだけど受かった。
[勉強時間計]
20(分)*10(日) = 200分+α
[試験概要]
試験時間:50分
問題数:約50問(自分は51問だった)
[合格基準]
・IT理論
・製品技術
・ソリューション
の三つの分野で出題され、それぞれの分野で合格点を満たし、
かつ合計スコアが合格ラインを上回れば合格。
採点基準などは非公開で、試験終了後に800点満点換算された得点が
表示される。
三つの分野が合格ライン+合計スコアが7割の560点以上であること。