フォトレタッチソフト開発記第六回>YCrCb成分調整

 前回作った成分調整ダイアログで、色空間としてYCrCbも扱えるようにしましょう。これによりピクセルの色を明度と色の情報に分けて扱えるようになるので、より感覚的な調整が可能になるでしょう。YCrCbを扱うには、まずC32DIBクラスにビットマップと同じ大きさのバッファを作成し、そこに現在のピクセル列からYCrCbバッファを作ったり逆にYCrCbバッファからRGBピクセル列を再構成するメンバ関数を作ります。そして、ダイアログにRGBYCrCb切り替えのメニューを作ればよいですね。
 今回は、他にも色数や大きさといった画像情報の表示や色の反転処理を追加してみました。これで、かなり実用的になって来たと言えるのではないでしょうか?

YCrCbバッファの追加

 最初にC32DIBクラスにYCrCbを扱う機能を追加しましょう。YCrCbには各8ビットを割り当てますが、バッファとしてはピクセル列同様1ピクセル4バイト(32ビット)とし、このバッファはコンストラクタのほかリサイズやファイル読み込みなどビットマップの大きさが変わる度に確保しなおします。例えば、コンストラクタのメモリ確保では以下のようにRGB/YCrCbバッファ用にdwWidth*dwHeight*4*2バイト確保するようにしました。YCrCbバッファのアドレスは、LPDWORD型ポインタlpBufferに格納します。

  // DIB用メモリ確保
  lpDIB=GlobalAlloc(GPTR,sizeof(BITMAPINFO)+dwWidth*dwHeight*4*2);
  // ポインタ設定
  lpbiInfo=(LPBITMAPINFO)lpDIB;
  lpPixels=(LPDWORD)((LPBYTE)lpDIB+sizeof(BITMAPINFO));
  lpBuffer=lpPixels+dwWidth*dwHeight;

 続いて、ピクセル列の内容をYCrCbとしてバッファに格納するsetYUVtoBuffer()を定義します。この関数を呼び出すと、現在のピクセル列(RGBピクセル列)の内容がYCrCb形式でバッファlpBufに書き込まれるわけですね。

  void C32DIB::setYUVtoBuffer() {

      DWORD y,u,v;
      BYTE r,g,b;

      for (DWORD i=0;i<dwHeight;i++) /* ピクセル列からYCrCbバッファ作成 */
          for (DWORD j=0;j<dwWidth;j++) {

              r=getR(j,i);
              g=getG(j,i);
              b=getB(j,i);

              y=(DWORD)(0.299*r+0.587*g+0.114*b);
              u=(DWORD)(-0.169*r-0.332*g+0.5*b+128);
              v=(DWORD)(0.5*r-0.419*g-0.081*b+128);

              if (y<0)
                  y=0;
              if (y>255)
                  y=255;

              if (u<0)
                  u=0;
              if (u>255)
                  u=255;

              if (v<0)
                  v=0;
              if (v>255)
                  v=255;

              setY(j,i,y);
              setU(j,i,u);
              setV(j,i,v);

          }

  }

 上で使っているsetY()などはバッファのY成分などにアクセスする関数です。これも、値を書き込むsetと値を取得するget関数を定義しておきましょう。

  BYTE C32DIB::getY(DWORD dwX,DWORD dwY) {

      if (dwX<dwWidth && dwY<dwHeight)
          return (BYTE)((getBuffer(dwX,dwY) & 0x00ff0000) >>16);
      else
          return 0;

  }

  bool C32DIB::setY(DWORD dwX,DWORD dwY,int iC) {

      if (dwX>=getWidth() || dwY>=getHeight())
          return FALSE;

      if (iC<0)
          iC=0;
      else if (iC>255)
          iC=255;

      LPBYTE w=(LPBYTE)(lpBuffer+dwX+dwY*getWidth())+2;
      *w=(BYTE)iC;

      return TRUE;

  }

 続いて、YCrCbからピクセル列を作るsetRGBtoPixels()。この辺りの処理は、DIB・グラフィック処理実験室で既に何度かやってますね。

  void C32DIB::setRGBtoPixels() {

      int y,u,v,r,g,b;

      for (DWORD i=0;i<dwHeight;i++)
          for (DWORD j=0;j<dwWidth;j++) {

              y=getY(j,i);
              u=getU(j,i)-128;
              v=getV(j,i)-128;

              r=(DWORD)(y+1.402*v);
              g=(DWORD)(y-0.344*u-0.714*v);
              b=(DWORD)(y+1.772*u);

              if (r<0)
                  r=0;
              if (r>255)
                  r=255;

              if (g<0)
                  g=0;
              if (g>255)
                  g=255;

              if (b<0)
                  b=0;
              if (b>255)
                  b=255;

              setR(j,i,r);
              setG(j,i,g);
              setB(j,i,b);

          }

  }

 実際にYCrCbを処理する時には、まずsetYUVtoBuffer()でバッファにYCrCbの情報を格納して処理を行い、その結果をsetRGBtoPixels()でピクセル列に反映させてから描画します。

成分調整ダイアログとその他の処理

 これでYCrCbを扱う基本的な機能は出来たので、成分調整ダイアログにメニューを追加しYCrCbも扱えるようにしましょう。メニューは、ダイアログのWM_CREATEメッセージ処理時に

  hwMenu=CreateWindow("COMBOBOX",NULL,WS_CHILD|WS_VISIBLE|CBS_DROPDOWN,
    4,4,72,90,hwnd,(HMENU)0,hInst,NULL);

  SendMessage(hwMenu,CB_ADDSTRING,-1,(LPARAM)"RGB");
  SendMessage(hwMenu,CB_ADDSTRING,-1,(LPARAM)"YCrCb");
  SendMessage(hwMenu,CB_SETCURSEL,0,1);

 としてRGBYCrCbという2つの項目を持つコンボボックスを作成します。これで、SendMessage(hwMenu,CB_GETCURSEL,0,0)のようにコンボボックスにメッセージを送ると現在選択されている項目を取得できるので、後は選択されている項目に応じて処理するだけですね。

 ただ、このままだとラベルの表示がRGBのままです。これを、YCrCbの時にはYCrCbとして表示されるようにしましょう。そのためには、コンボボックスの項目が選択されると送られてくるWM_COMMANDメッセージ処理で現在の選択項目を取得しラベルの文字列を設定します。

  // トラックバーの位置から補正値を算出
  iDC1=SendMessage(hwC1,TBM_GETPOS,0,0)-64;
  iDC2=SendMessage(hwC2,TBM_GETPOS,0,0)-64;
  iDC3=SendMessage(hwC3,TBM_GETPOS,0,0)-64;

  if (SendMessage(hwMenu,CB_GETCURSEL,0,0)==0) {

      // ラベルに現在の値を表示
      wsprintf(str,"R:%d",iDC1);
      SetWindowText(hwL1,str);
      wsprintf(str,"G:%d",iDC2);
      SetWindowText(hwL2,str);
      wsprintf(str,"B:%d",iDC3);
      SetWindowText(hwL3,str);

  } else {

      // ラベルに現在の値を表示
      wsprintf(str,"Y:%d",iDC1);
      SetWindowText(hwL1,str);
      wsprintf(str,"Cr:%d",iDC2);
      SetWindowText(hwL2,str);
      wsprintf(str,"Cb:%d",iDC3);
      SetWindowText(hwL3,str);

  }

 実際にYCrCbの値をいじる処理は前回RGBに対して行ったのと同様、ビットマップ全体のYCrCbを変更するtrans関数を作りました。

  void C32DIB::transY(int iV) {

      int y;

      for (DWORD i=0;i<getHeight();i++)
          for (DWORD j=0;j<getWidth();j++) {

              y=getY(j,i)+iV;
              setY(j,i,y);

          }

  }

 後の処理はRGBと同じですね。トラックバーが移動したら現在位置を読んで、表示用ビットマップにその値でYCrCb補正をかけ、実行ボタンが押されたら実際にビットマップの方も補正する、と。

 ダイアログが出来たついでに他の機能もいくつか追加しておきましょう。まず、ビットマップの情報表示。今回は、ビットマップの大きさとビットマップの色数を表示してみます。このうち大きさは単にgetWidth(), getHeight()すれば良いので、C32DIBにピクセル列の色数を数えるメンバ関数を追加しました。処理アルゴリズムは、フラグ用のバッファを確保してそこにピクセル列内の色情報を書き込んでいく、というもの(詳しくはこちら)です。

  DWORD C32DIB::getColors() { // ビットマップ内の色数取得

      DWORD i,j,dwBit,dwByte,dwCount=0,dwPixel;
      LPBYTE fColors;

      fColors=(LPBYTE)GlobalAlloc(GPTR,256*256*32);

      for (i=0;i<dwHeight;i++)
          for (j=0;j<dwWidth;j++) {

              dwPixel=getPixel(j,i);

              /* dwPixelビット目の位置を計算 */
              dwByte=dwPixel/8;
              dwBit=dwPixel%8;

              /* フラグ領域のdwPixelビット目を検査 */
              if ((fColors[dwByte] & (1 << dwBit))==0) {

                  /* フラグ領域のdwPixelビット目を立てる */
                  fColors[dwByte]=fColors[dwByte]|(1 << dwBit);
                  dwCount++; /* 色数カウンタ更新 */

              }

          }

      GlobalFree(fColors);

      return dwCount;

  }

 色反転は、単にピクセルの値をxorしているだけです。

プログラム

 適当な24ビットフルカラーBMPをドロップしたら調整・変換メニューで成分調整ダイアログを呼び出してRGB/YCrCb成分をいじってみてください。

プログラムダウンロード(saib6.lzh/39KB)
戻る