/*
  RgbYcc.java
  Class for converting RGB to YCbCr and vice versa.
  Integer arithmetic is used.
  The class will be used in subsequent chapters.
*/

/*
  Convert from RGB to YCbCr using  ITU-R recommendation BT.601.
     Y = 0.299R + 0.587G + 0.114B
    Cb = 0.564(B - Y ) + 0.5
    Cr = 0.713(R - Y ) + 0.5

  Integer arithmetic is used to speed up calculations. 
  Note:
       2^16 = 65536 
       kr = 0.299 = 19595 / 2^16
       kg = 0.587 = 38470 / 2^16
       Kb = 0.114 =  7471 / 2^16
       0.5 = 128 / 255
       0.564 = 36962 / 2^16
       0.713 = 46727 / 2^16
       1.402 = 91881 / 2^16
       0.701 = 135 / 255
       0.714 = 46793 / 2^16
       0.344 = 22544 / 2^16
       0.529 = 34668 / 2^16
       1.772 = 116129 / 2^16
       0.886 = 226 / 255
*/

import java.io.*;

class RgbYcc {
  public void rgb2ycbcr( RGB rgb, YCbCr ycc )
  {
      //coefs summed to 65536 ( 1 << 16 ), so Y is always within [0, 255]
      ycc.Y = (19595 * rgb.R + 38470 * rgb.G + 7471 * rgb.B ) >> 16;
      ycc.Cb = ( 36962 * ( rgb.B - ycc.Y ) >> 16 ) + 128 ;
      ycc.Cr = ( 46727 * ( rgb.R - ycc.Y )  >> 16 ) + 128 ;
  }

  //just convert an RGB pixel to Y component
  public int rgb2y( RGB rgb )
  {
    int y;

    y = ( 19595 * rgb.R + 38470 * rgb.G + 7471 * rgb.B ) >> 16;
    return y;
  }
  
  //limit value to lie within [0,255]
  public RGB chop ( RGB rgb )
  {
      if ( rgb.R < 0 ) rgb.R = 0;
      else if ( rgb.R > 255 ) rgb.R = 255;
      if ( rgb.G < 0 ) rgb.G = 0;
      else if ( rgb.G > 255 ) rgb.G = 255;
      if ( rgb.B < 0 ) rgb.B = 0;
      else if ( rgb.B > 255 ) rgb.B = 255;
  
     return rgb;
  }

  /*
    Convert from YCbCr to RGB domain. Using ITU-R standard:
     R = Y + 1.402Cr - 0.701
     G = Y - 0.714Cr - 0.344Cb + 0.529
     B = Y + 1.772Cb - 0.886
    Integer arithmetic is used to speed up calculations.
  */
  public void ycbcr2rgb( YCbCr ycc, RGB rgb )
  {
    int x; 
    int Y = ( int ) ycc.Y << 16;  //same as multiply 65536

    rgb.R = ( ( ycc.Y           + ( 91881 * ycc.Cr   >> 16 ) - 179 ) );
    rgb.G = ( ( ycc.Y -(( 22544 * ycc.Cb + 46793 * ycc.Cr ) >> 16) + 135) );
    rgb.B = ( ( ycc.Y  + (116129 * ycc.Cb   >> 16 ) - 226 ) );
    chop ( rgb );	    //enforce values to lie within [0,255]
  }

  /*
    Convert an RGB macro block ( 16x16 ) to 
    4:2:0 YCbCr sample blocks ( six 8x8 blocks ).
  */
  
  void macroblock2ycbcr ( RGB_MACRO rgb_macro, YCbCr_MACRO ycbcr_macro )
  {
    int i, j, k, r;
    YCbCr ycc = new YCbCr();

    r = k = 0;
    for ( i = 0; i < 16; ++i ) {
      for ( j = 0; j < 16; ++j ) {
        if ( ( i & 1 ) == 0 && ( j & 1 ) == 0 ) { //need one Cb, Cr for every 4 pixels
          rgb2ycbcr ( rgb_macro.rgb[r], ycc );	  //convert to Y and Cb, Cr values
          ycbcr_macro.Y[r] = ycc.Y;
          ycbcr_macro.Cb[k] = ycc.Cb;
          ycbcr_macro.Cr[k] = ycc.Cr;
          k++;
        } else {              //only need the Y component for other 3 pixels
          ycbcr_macro.Y[r] = rgb2y ( rgb_macro.rgb[r] );	
        }
        r++;                  //convert every pixel for Y
      }
    }
  }

  /*
    Convert the six 8x8 YCbCr sample blocks to RGB macroblock ( 16x16 ).
  */
  void ycbcr2macroblock( YCbCr_MACRO ycbcr_macro, RGB_MACRO rgb_macro )
  {
    int i, j, k, r;
    YCbCr ycc = new YCbCr();

    r = k = 0;
    for ( i = 0; i < 16; ++i ) {
      for ( j = 0; j < 16; ++j ) {
        //one Cb, Cr has been saved for every 4 pixels
        if ( ( i & 1 ) == 0 && ( j & 1 ) == 0 ) { 
          ycc.Y = ycbcr_macro.Y[r];
          ycc.Cb = ycbcr_macro.Cb[k];
          ycc.Cr = ycbcr_macro.Cr[k];
          k++;
        } else {
          ycc.Y = ycbcr_macro.Y[r];
        }
        ycbcr2rgb ( ycc, rgb_macro.rgb[r] );
        r++;
      }
    }
  }

  
  //print a macro block
  //  (Better put all print functions in Printer class in util directory.)
  void print_ymacro ( YCbCr_MACRO ycbcr_macro )
  {
    int i, j, k;
    k = 0;
    System.out.printf("\nY Components\n");
   
    for ( i = 0; i < 16; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 16; ++j ) {
        System.out.printf("%4d", ycbcr_macro.Y[k++] ); 
        if ( j % 4 == 3 ) System.out.printf ( " | " );      
      }
      if ( i % 8 == 7 )
         System.out.printf("\n---------------------------------------\n");
    }
    System.out.printf("\n---------------------------------------\n");
    System.out.printf("\nCb Components\n");
    k = 0;
    for ( i = 0; i < 8; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 8; ++j ){
	System.out.printf("%4d", ycbcr_macro.Cb[k++] );
      }
    } 
    System.out.printf("\n---------------------------------------\n");
    System.out.printf("\nCr Components\n");
    k = 0;
    for ( i = 0; i < 8; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 8; ++j ){
	System.out.printf("%4d", ycbcr_macro.Cr[k++] );
      }
    }  
  }

  //print an rgb macro block
  void print_rgbmacro ( RGB_MACRO rgb_macro )
  {
    int i, j, k;
    k = 0;
    System.out.printf("RGB values:\n");
    for ( i = 0; i < 16; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 16; ++j ) {
        System.out.printf("%4d", rgb_macro.rgb[k++].R ); 
        if ( j % 4 == 3 ) System.out.printf ( " | " );      
      }
      if ( i % 8 == 7 )
        System.out.printf("\n---------------------------------------\n");
    }
    System.out.printf("\n---------------------------------------\n");
    k = 0;
    for ( i = 0; i < 16; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 16; ++j ){
	System.out.printf("%4d", rgb_macro.rgb[k++].G );
        if ( j % 4 == 3 ) System.out.printf ( " | " );      
      }
    } 
    System.out.printf("\n---------------------------------------\n");
    k = 0;
    for ( i = 0; i < 16; ++i ) {
      System.out.printf("\n");
      for ( j = 0; j < 16; ++j ){
	System.out.printf("%4d", rgb_macro.rgb[k++].B );
        if ( j % 4 == 3 ) System.out.printf ( " | " );      
      }
    }
  }
}
