ビットマップの縮小処理

今回は画像の「縮小」をやってみます。

整数分の一の縮小なら簡単ですが、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ビットのフルカラービットマップだけです。

プログラムソース表示

 実際に処理してみると、隣のピクセルと「混ぜて」いるので文字などは少しぼやけた感じになりますが、写真はかなり良い感じですね。