YCrCbの調整

 今回は、ビットマップを読み込んでYCrCbに変換し、YCrCbの各成分をトラックバーで変更する画像処理プログラムを作ってみます。ちょうど、フォトレタッチソフトで明度や色差をいじるような感じですね。

YCrCbバッファの作成

 ビットマップを読み込んでYCrCbに変換する部分、またYCrCbからDIB用のRGBフルカラーピクセル列を作成する部分は前回と同じです。ビットマップをYCrCbに変換したらバッファに保存しておき、YCrCbを変更する時にはこのバッファから読み出した値に変更を加えて別に用意した表示用YCrCbバッファに書き込むようにしましょう。

  BOOL readBMP(LPCTSTR lpszFn) { /* ビットマップ読み込み */

      DWORD offset,dummy,i,j;
      double y,u,v,r,g,b;

      HANDLE fh;

      if (loaded) { /* 以前確保したバッファを解放 */

          GlobalFree(lpDIB);
          GlobalFree(lpYUV1);
          GlobalFree(lpYUV2);

      }

      fh=CreateFile(lpszFn,GENERIC_READ,0,NULL,OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,NULL); /* ファイルオープン */

      lpDIB=(LPBYTE)GlobalAlloc
         (GPTR,GetFileSize(fh,NULL)); /* DIBバッファ確保 */

      ReadFile(fh,lpDIB,GetFileSize(fh,NULL),&dummy,NULL);

      lpbiInfo=(LPBITMAPINFO)(lpDIB+sizeof(BITMAPFILEHEADER));

      offset=*(LPDWORD)(lpDIB+10);
      lpPixel=lpDIB+offset; /* ピクセル列の先頭アドレス */

      CloseHandle(fh);

      /* 24ビットフルカラービットマップでなければ無効 */
      if (lpDIB[0]!='B' || lpbiInfo->bmiHeader.biBitCount!=24) {

          GlobalFree(lpDIB);
          GlobalFree(lpYUV1);
          GlobalFree(lpYUV2);

          loaded=FALSE;

          return FALSE;

      }

      /* ビットマップの大きさ保存 */
      dwWidth=lpbiInfo->bmiHeader.biWidth;
      dwHeight=lpbiInfo->bmiHeader.biHeight;

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

      /* YCrCbバッファ(最初の状態) */
      lpYUV1=GlobalAlloc(GPTR,dwWidth*dwHeight*3);
      /* YCrCbバッファ(表示用) */
      lpYUV2=GlobalAlloc(GPTR,dwWidth*dwHeight*3);

      for (i=0;i<dwHeight;i++) /* YCrCbに変換し保存 */
          for (j=0;j<dwWidth;j++) {

              b=lpPixel[j*3+i*dwLength];
              g=lpPixel[j*3+i*dwLength+1];
              r=lpPixel[j*3+i*dwLength+2];

              y=0.299*r+0.587*g+0.114*b;
              u=-0.169*r-0.332*g+0.5*b+128;
              v=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;

               /* バッファに保存 */
               lpYUV1[j*3+i*dwWidth*3]=(BYTE)y;
               lpYUV1[j*3+i*dwWidth*3+1]=(BYTE)u;
               lpYUV1[j*3+i*dwWidth*3+2]=(BYTE)v;

          }

      /* YCrCbを表示用バッファにコピー */
      CopyMemory(lpYUV2,lpYUV1,dwWidth*dwHeight*3);

      YuvToDib(); /* 表示用DIB作成 */

      /* トラックバーの位置初期化 */
      SendMessage(hwTBY,TBM_SETPOS,1,48);
      SendMessage(hwTBU,TBM_SETPOS,1,48);
      SendMessage(hwTBV,TBM_SETPOS,1,48);

      EnableWindow(hwTBY,TRUE);
      EnableWindow(hwTBU,TRUE);
      EnableWindow(hwTBV,TRUE);

      iDY=0;
      iDU=0;
      iDV=0;

      return TRUE;

  }

 まずlpYUV1に最初の状態を保存して、表示用バッファlpYUV2にコピーするわけですね。コピーしたらlpYUV2から表示用DIBのピクセル列を作成する関数YuvToDib()でピクセル列を作ります。

  VOID YuvToDib(void) { /* 表示用DIB作成 */

      DWORD i,j;
      double y,u,v,r,g,b;

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

              y=lpYUV2[j*3+i*dwWidth*3];
              u=lpYUV2[j*3+i*dwWidth*3+1]-128;
              v=lpYUV2[j*3+i*dwWidth*3+2]-128;

              r=y+1.402*v;
              g=y-0.344*u-0.714*v;
              b=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;

              lpPixel[j*3+i*dwLength]=(BYTE)b;
              lpPixel[j*3+i*dwLength+1]=(BYTE)g;
              lpPixel[j*3+i*dwLength+2]=(BYTE)r;

          }

  }

 この関数は、単にYCrCbからRGBのピクセル列を作っているだけです。DIBヘッダの方はビットマップを読み込んだ時にすでに作成済みですから、これで表示用DIBが出来た事になります。

YCrCbの変更

 YCrCbの値を変える時は、読み込んだ時の状態を保存したlpYUV1からYCrCbの値を読み出し、変更を加えながら表示用バッファlpYUV2にコピーして行きます。これで、何度変更を加えても「最初の状態のビットマップ」に対して変更を加える事が出来るわけですね。

  void changeYUV(int iDY,int iDU,int iDV) { /* YCrCb変更 */

      DWORD i,j;
      int y,u,v;

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

              y=lpYUV1[j*3+i*dwWidth*3]+iDY;
              u=lpYUV1[j*3+i*dwWidth*3+1]+iDU;
              v=lpYUV1[j*3+i*dwWidth*3+2]+iDV;

              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;

              lpYUV2[j*3+i*dwWidth*3]=y;
              lpYUV2[j*3+i*dwWidth*3+1]=u;
              lpYUV2[j*3+i*dwWidth*3+2]=v;

          }

      YuvToDib();

      InvalidateRgn(hwMain,NULL,FALSE);
      UpdateWindow (hwMain);             /* 再描画 */

  }

 変更する量は、変数iDY, iDU, iDVで指定します。プログラムでは、YCrCbそれぞれにトラックバーを作っておいて、トラックバーの値が変更されたらこの関数を呼ぶようにしました。トラックバーは最大値を96にして、±48の範囲に対応させます。

  case WM_HSCROLL:

      iDY=SendMessage(hwTBY,TBM_GETPOS,0,0)-48;
      iDU=SendMessage(hwTBU,TBM_GETPOS,0,0)-48;
      iDV=SendMessage(hwTBV,TBM_GETPOS,0,0)-48;

      changeYUV(iDY,iDU,iDV);

      break;

プログラム

 まず、適当なフルカラービットマップファイルをドロップしてください。ドロップするとビットマップのRGBピクセル列がYCrCb形式に変換されて表示され、トラックバーでYCrCb成分の値を変更できるようになります。

プログラムソース表示

大きなビットマップだと変換に時間がかかるので、小さ目のビットマップを読み込んでいろいろいじってみると良いでしょう。


戻る