colour jpeg block black by modifying AC coefficient

23 views Asked by At

I'm trying to colour a region of a JPEG image black on DCT space by manipulating the individual DCT coefficients values on libjpeg.

I get that the DC coefficient specifies the hue. So, ignoring up/down sampling issues, one should be able to colour a whole block by zeroing all AC coefficients and manipulating the DC coefficients. However, I can't figure out what is the range of values.

For example, to colour the top left block with midpoint grey, one can:

int row = 0;
int col = 0;

jvirt_barray_ptr *src_coef_arrays = jpeg_read_coefficients(&src.info);
for (int ci = 0; ci < src.info.num_components; ci++) {

    JBLOCKARRAY buffer = src.info.mem->access_virt_barray(
        (j_common_ptr)&src.info,
        src_coef_arrays[ci],
        row,
        comp_info.v_samp_factor,
        TRUE
    );
    for (int k = 0; k < DCTSIZE2; k++)
        blk_buf[0][col][k] = 0;
}

Instead of the midpoint grey, assuming YCbCr, one can darken/lighten by changing the DC coefficient of the luma component only :

  if (ci == 0)  // luma component
      blk_buf[0][col][0] = -50;  // tweak DC coeff of luma component
  else
      blk_buf[0][col][0] = 0;    // zero DC coeff of chroma components
  for (int k = 1; k < DCTSIZE2; k++)
      blk_buf[0][col][k] = 0;    // zero all AC coeffs

However, I haven't figured out how to go from a specific YCbCr value to the DC values, namely, what is a DC value of black? And how to go from any colour to the DC values?

1

There are 1 answers

0
carandraug On

There are two key points about manipulating the "raw" DCT coefficients in libjpeg:

  1. the coefficients returned are already/still quantized
  2. the coefficients are scaled up by a factor of 8

That factor of 8 is mentioned on jdct.h (an internal header file of libjpeg):

/*
 [...]
 * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE).
 * The DCT outputs are returned scaled up by a factor of 8; they therefore
 * have a range of +-8K for 8-bit data, +-128K for 12-bit data.  This
 [...]
 */

So, the procedure to find the required DC values for a given colour is:

  1. Convert the wanted color to the output jpeg colour space (typically YCbCR). Check info.jpeg_color_space. Valid jpeg colour spaces are:

    • JCS_YCbCr
    • JCS_GRAYSCALE
    • JCS_RGB
    • JCS_CMYK
    • JCS_YCCK
  2. Convert the values to the -CENTERJSAMPLE : (CENTERJSAMPLE-1) range times 8. Assuming you want colour black, and JPEG colour space is YCbCR, since black is YCbCr(-128, 0, 0) (assuming YCbCr notation taking values between -128 and 127), then:

    int DC_black_Y = -CENTERJSAMPLE *8;
    int DC_black_Cb = 0;
    int DC_black_Cr = 0;
    
  3. Divide the value by the first element of the quantizer matrix (the quantizer value for the DC value) for that image component. You can get the quantizer value each component uses with:

    for (int ci = 0; ci < info.num_components; ci++) {
      const int quant_tbl_no = info.comp_info[ci].quant_tbl_no;
      const UINT16 dc_quant = info.quant_tbl_ptrs[quant_tbl_no]->quantval[0];
      [...]
    
  4. round the value away from zero (ceil if positive, floor if negative)

    // Round towards closest infinity (away from zero)
    // assuming dc_value and dc_quant are integers
    if (dc_value < 0)
      dc_value = (dc_value - dc_quant +1) / dc_quant;
    else if (dc_value > 0)
      dc_value = (dc_value + dc_quant -1) / dc_quant;
    // else do nothing, it's zero