ビットマップの縮小処理
今回は画像の「縮小」をやってみます。
整数分の一の縮小なら簡単ですが、2/3のような場合はどうすれば良いのか、考えてみましょう。まあ、一番簡単な方法としてDIBセクションを作ってGDIで縮小描画する、という手もあるのですが(^^;。
画像の縮小処理のアルゴリズム
n分の一の縮小は、nドットを1つにまとめるだけです。単純に「間引く」よりも、nドットのRGB平均をとってそれを1ドットにしたほうが良いでしょう。
例えば、バッファlpBMPにあるiWidth×iHeightピクセルの24ビットフルカラーを1/2にする場合は以下のようになります。
/* 作業用バッファをクリア */
FillMemory(lpWork,iLength*iHeight,0xff);
for (i=0;i<iHeight;i++) /* 横方向1/2に縮小 */
for (j=0;j<iWidth/2;j++) {
lpWork[j*3+i*iLength]= /* B成分 */
(lpBMP[(j*2)*3+i*iLength]+lpBMP[(j*2+1)*3+i*iLength])/2;
lpWork[j*3+i*iLength+1]= /* G成分 */
(lpBMP[(j*2)*3+i*iLength+1]+lpBMP[(j*2+1)*3+i*iLength+1])/2;
lpWork[j*3+i*iLength+2]= /* R成分 */
(lpBMP[(j*2)*3+i*iLength+2]+lpBMP[(j*2+1)*3+i*iLength+2])/2;
}
for (i=0;i<iWidth;i++) /* 縦方向に1/2に縮小 */
for (j=0;j<iHeight/2;j++) {
lpWork[i*3+j*iLength]=
(lpWork[i*3+j*2*iLength]+lpWork[i*3+(j*2+1)*iLength])/2;
lpWork[i*3+j*iLength+1]=
(lpWork[i*3+j*2*iLength+1]+lpWork[i*3+(j*2+1)*iLength+1])/2;
lpWork[i*3+j*iLength+2]=
(lpWork[i*3+j*2*iLength+2]+lpWork[i*3+(j*2+1)*iLength+2])/2;
}
for (i=j;i<iHeight;i++)
FillMemory(lpWork+i*iLength,iLength,0xff);
CopyMemory(lpBMP,lpWork,iLength*iHeight);
この例では、縮小された画像がDIBの左下のほうに描画され、余白は白で塗りつぶされます。もちろん、縦・横と分けて処理をせずに2×2のブロック単位で処理しても良いでしょう。
次に、非整数分の一の例として2/3に縮小する場合。これは「3つのピクセルを2つにする」わけですから、「両端と中心のピクセルを平均する」事にしました。
つまり、元画像の3つのピクセルのうち右端と中央の平均を縮小画像の右ピクセル、元画像の中央と左端の平均を縮小画像の左ピクセルにするのです。
この処理を行うプログラムは、以下のようになります。
FillMemory(lpWork,iLength*iHeight,0xff);
for (i=0;i<iHeight;i++) /* 横方向2/3に縮小 */
for (j=0;j<iWidth/3;j++) {
lpWork[j*2*3+i*iLength]= /* 左側ピクセル */
(lpBMP[j*3*3+i*iLength]+lpBMP[(j*3+1)*3+i*iLength])/2;
lpWork[j*2*3+i*iLength+1]=
(lpBMP[j*3*3+i*iLength+1]+lpBMP[(j*3+1)*3+i*iLength+1])/2;
lpWork[j*2*3+i*iLength+2]=
(lpBMP[j*3*3+i*iLength+2]+lpBMP[(j*3+1)*3+i*iLength+2])/2;
lpWork[(j*2+1)*3+i*iLength]= /* 右側ピクセル */
(lpBMP[(j*3+1)*3+i*iLength]+lpBMP[(j*3+2)*3+i*iLength])/2;
lpWork[(j*2+1)*3+i*iLength+1]=
(lpBMP[(j*3+1)*3+i*iLength+1]+lpBMP[(j*3+2)*3+i*iLength+1])/2;
lpWork[(j*2+1)*3+i*iLength+2]=
(lpBMP[(j*3+1)*3+i*iLength+2]+lpBMP[(j*3+2)*3+i*iLength+2])/2;
}
for (i=0;i<iWidth;i++) /* 縦方向2/3に縮小 */
for (j=0;j<iHeight/3;j++) {
lpWork[i*3+j*2*iLength]= /* 下側ピクセル */
(lpWork[i*3+j*3*iLength]+lpWork[i*3+(j*3+1)*iLength])/2;
lpWork[i*3+j*2*iLength+1]=
(lpWork[i*3+j*3*iLength+1]+lpWork[i*3+(j*3+1)*iLength+1])/2;
lpWork[(i*3+j*2*iLength)+2]=
(lpWork[i*3+j*3*iLength+2]+lpWork[i*3+(j*3+1)*iLength+2])/2;
lpWork[i*3+(j*2+1)*iLength]= /* 上側ピクセル */
(lpWork[i*3+(j*3+1)*iLength]+lpWork[i*3+(j*3+2)*iLength])/2;
lpWork[i*3+(j*2+1)*iLength+1]=
(lpWork[i*3+(j*3+1)*iLength+1]+lpWork[i*3+(j*3+2)*iLength+1])/2;
lpWork[i*3+(j*2+1)*iLength+2]=
(lpWork[i*3+(j*3+1)*iLength+2]+lpWork[i*3+(j*3+2)*iLength+2])/2;
}
for (i=j*2;i<iHeight;i++)
FillMemory(lpWork+i*iLength,iLength,0xff);
CopyMemory(lpBMP,lpWork,iLength*iHeight);
さて、今回のプログラムにはiLengthという変数が出てきますね。
これは、バッファの「横方向の大きさ」を記憶したもので、ビットマップの幅が4の倍数ピクセルの場合は単純に幅を3倍(RGBそれぞれ1バイト)にし、4の倍数でなかった場合は以下のように計算しています。
iLength=iWidth*3+(4-(iWidth*3) % 4);
これは、4の倍数バイトに「切り上げる」処理です。
プログラム
プログラムを起動したら、適当なビットマップファイルをドラッグ&ドロップするか、「読みこみ」ボタンで読みこんでください。その後、「1/2」「2/3」ボタンで縮小できます。
ただし、読みこめるのは24ビットのフルカラービットマップだけです。
実際に処理してみると、隣のピクセルと「混ぜて」いるので文字などは少しぼやけた感じになりますが、写真はかなり良い感じですね。