横方向の縮小

 以前試した縮小処理では、縮小率が簡単な比で表される場合のみを想定し、縮小するビットマップと元ビットマップの対応付けもあらかじめ決めていました。今回は、もっと汎用的に画像を任意の縮小率で縮小できるようにしてみましょう。とりあえず今回は横方向の縮小を試してみますが、これと同様の事を縦方向にも行えば、ビットマップを任意の比率で縮小できるはずですね。

非整数ピクセルの重み

 縮小処理は、縮小後の各ピクセルに元画像のピクセルを対応させる形で行います。この時問題になるのは、ピクセルの境にある半端な部分です。例えば、0.6倍に縮小する場合、元ピクセルの1ピクセル目は0-0.6に、2ピクセル目は0.6-1.2に対応します。つまり、元画像の2ピクセル目は縮小後の1ピクセル目と2ピクセル目に含まれ、しかも1ピクセル目には0.4、2ピクセル目には0.2と半端な割合で含まれるわけです。縮小の時には、1ピクセル目に元画像の1ピクセル目と2ピクセル目をその重み(1ピクセル目は0.6、2ピクセル目は0.4)をかけて含ませる必要があるでしょう。また、2ピクセル目に付いても同様に対応する元画像のピクセルがどれだけの重みを持つか、考えなければいけません。

 この状況を一般化すると、問題になるのは各ピクセルに対応する元画像の最初と最後のピクセルである事がわかります。最初と最後に挟まれた間のピクセルの重みは常に縮小率と同じだからです。例えば、0.6倍にする場合、縮小後の2ピクセル目には元画像の2,3,4ピクセルが対応しますが、このうち3ピクセル目の重みは縮小率と同じ0.6になります。という事は、あるピクセルに対応する元画像の最初と最後のピクセルに対してそのピクセルがどれだけ含まれるかを求め、他のピクセルは比率と同じ重みをかければ良い事になりますね。
 dwSXを最初のピクセル、dwEXを最後のピクセルとすれば以下のような感じになるでしょう。

  for (i=dwSX;i<dwEX+1;i++) {

    if (i==dwSX){

      lfW=最初のピクセルが含まれる量

      lfR+=最初のピクセルのR成分*lfW
      lfG+=最初のピクセルのG成分*lfW
      lfB+=最初のピクセルのB成分*lfW

      lfSum+=lfW;

    } else if (i==dwEX)

      lfW=最後のピクセルが含まれる量

      lfR+=最初のピクセルのR成分*lfW
      lfG+=最初のピクセルのG成分*lfW
      lfB+=最初のピクセルのB成分*lfW

      lfSum+=lfW;

    else {

      lfW=縮小率

      lfR+=対応ピクセルのR成分*lfW
      lfG+=対応ピクセルのG成分*lfW
      lfB+=対応ピクセルのB成分*lfW

      lfSum+=lfW;

    }

  縮小後ピクセルのR成分=(lfR/lfSum)
  縮小後ピクセルのG成分=(lfG/lfSum)
  縮小後ピクセルのB成分=(lfB/lfSum)

 縮小後のピクセルxに含まれる最初のピクセルdwSXは、座標xに縮小率の逆数をかけ、最後のピクセルdwEXは「次のピクセルに対応する最初のピクセルの前」なので以下の式で計算できます。floor()は小数点以下切り捨て、ceil()は小数点以下切り上げの関数である点に注意してください。

  dwSX=(DWORD)floor(x*(1/lfRate));
  dwEX=(DWORD)ceil((x+1)*(1/lfRate))-1;

 では、最初と最後のピクセルの重みはどうやって求めれば良いのでしょうか。まず、最初のピクセルについては、縮小ピクセルの中で元画像から持ってきた最初のピクセルが占める最後の座標から縮小後のピクセルの最初の位置を引けば良いはずです。例えば、0.6倍にする時の2ピクセル目なら、最初のピクセルは元画像の2ピクセル目。2ピクセル目は1.2までを占めるので、これから縮小ピクセルの開始点1.0を引けば良いわけです。式で書けば、縮小ピクセルに対する開始ピクセルkの重みlfWは、

  lfW=(k+1)*lfRate-floor(k*lfRate);

 で求められる事になります。この式で縮小率lfRate=0.6k=1(2ピクセル目は0から始まる座標で言えば1)とすればlfWは0.2になりますね。

 最後のピクセルも同様に「最後のピクセルの開始点から縮小後の次のピクセルの開始点」を引けば良いはずですから、最後のピクセルkの重みは

  lfW=(j+1)-(k*lfRate);

 となるでしょう。以上の事をまとめると、幅dwWidth、高さdwHeightの24ビットDIBピクセルlpPixelの横方向を縮小する関数は以下のようになります。

  void resize(double lfRate) {

      DWORD dwWid2,dwLen2,dwSX,dwEX,i,j,k;
      LPBYTE lpPix2;
      double lfR,lfG,lfB,lfSum,lfW;

      dwWid2=(DWORD)(dwWidth*lfRate); /* 縮小後の横幅計算 */

      if ((dwWid2*3) % 4==0) /* バッファの1ラインの長さを計算 */
          dwLen2=dwWid2*3;
     else
          dwLen2=dwWid2*3+(4-(dwWid2*3) % 4);

      /* 作業用バッファ確保 */
      lpPix2=(LPBYTE)GlobalAlloc(GPTR,dwLen2*dwHeight);

      for (i=0;i<dwHeight;i++) /* 縮小処理 */
          for (j=0;j<dwWid2;j++) {

              /* (j,i)に対応する元画像のピクセルを計算 */
              dwSX=(DWORD)floor(j*(1/lfRate));
              dwEX=(DWORD)ceil((j+1)*(1/lfRate))-1;

              lfR=0;
              lfG=0;
              lfB=0;
              lfSum=0;

              for (k=dwSX;k<dwEX+1;k++) { /* 含まれるピクセルの成分を合計 */

                  if (k==dwSX) /* 最初のピクセル */
                      lfW=(k+1)*lfRate-floor(k*lfRate);
                  else if (k==dwEX) /* 最後のピクセル */
                      lfW=(j+1)-(k*lfRate);
                  else /* 間のピクセル */
                      lfW=lfRate;

                  lfB+=lpPixel[k*3+i*dwLength]*lfW;
                  lfG+=lpPixel[k*3+i*dwLength+1]*lfW;
                  lfR+=lpPixel[k*3+i*dwLength+2]*lfW;

                  lfSum+=lfW;

              }

              /* 縮小後ピクセルの成分を計算 */
              lpPix2[j*3+i*dwLen2]=(BYTE)(lfB/lfSum);
              lpPix2[j*3+i*dwLen2+1]=(BYTE)(lfG/lfSum);
              lpPix2[j*3+i*dwLen2+2]=(BYTE)(lfR/lfSum);

          }

      /* DIBのヘッダ・サイズ変数調整 */
      lpbiInfo->bmiHeader.biWidth=(DWORD)(dwWidth*lfRate);
      dwWidth=lpbiInfo->bmiHeader.biWidth;
      dwLength=dwLen2;

      /* 縮小結果をピクセル列にコピー */
      CopyMemory(lpPixel,lpPix2,dwLen2*dwHeight);

      GlobalFree(lpPix2);

  }

プログラム

 ウインドウに適当な24ビットBMPをドラッグ&ドロップしてからトラックバーで縮小率を設定し、縮小ボタンをクリックしてください。

プログラムソース表示

写真やエディタの画面(白地に黒い文字)のキャプチャ画面などを縮小し、その結果をドット絵でぃたなどの画像ソフトでピクセルごとに分析してみると良いでしょう。