/// <summary> /// Fast processing for the common case of 2:1 horizontal and 2:1 vertical. /// It's still a box filter. /// </summary> private void h2v2_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int inrow = 0; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; outIndex < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[outrow][outIndex] = invalue; outIndex++; output_data[outrow][outIndex] = invalue; outIndex++; } JpegUtils.jcopy_sample_rows(output_data, outrow, output_data, outrow + 1, 1, m_cinfo.m_output_width); inrow++; outrow += 2; } }
/// <summary> /// This version handles any integral sampling ratios. /// This is not used for typical JPEG files, so it need not be fast. /// Nor, for that matter, is it particularly accurate: the algorithm is /// simple replication of the input pixel onto the corresponding output /// pixels. The hi-falutin sampling literature refers to this as a /// "box filter". A box filter tends to introduce visible artifacts, /// so if you are actually going to use 3:1 or 4:1 sampling ratios /// you would be well advised to improve this code. /// </summary> private void int_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int h_expand = m_h_expand[m_currentComponent]; int v_expand = m_v_expand[m_currentComponent]; int inrow = 0; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { /* Generate one output row with proper horizontal expansion */ int row = m_upsampleRowOffset + inrow; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ int outIndex = 0; for (int h = h_expand; h > 0; h--) { output_data[outrow][outIndex] = invalue; outIndex++; } } /* Generate any additional output rows by duplicating the first one */ if (v_expand > 1) { JpegUtils.jcopy_sample_rows(output_data, outrow, output_data, outrow + 1, v_expand - 1, m_cinfo.m_output_width); } inrow++; outrow += v_expand; } }
/// <summary> /// Copy some rows of samples from one place to another. /// num_rows rows are copied from input_array[source_row++] /// to output_array[dest_row++]; these areas may overlap for duplication. /// The source and destination arrays must be at least as wide as num_cols. /// </summary> public static void jcopy_sample_rows(ComponentBuffer input_array, int source_row, byte[][] output_array, int dest_row, int num_rows, int num_cols) { for (int row = 0; row < num_rows; row++) { Buffer.BlockCopy(input_array[source_row + row], 0, output_array[dest_row + row], 0, num_cols); } }
#pragma warning disable IDE1006 // Naming Styles public static void jcopy_sample_rows(ComponentBuffer input_array, int source_row, ComponentBuffer output_array, int dest_row, int num_rows, int num_cols) #pragma warning restore IDE1006 // Naming Styles { for (var row = 0; row < num_rows; row++) { Buffer.BlockCopy(input_array[source_row + row], 0, output_array[dest_row + row], 0, num_cols); } }
/// <summary> /// Fast processing for the common case of 2:1 horizontal and 1:1 vertical. /// It's still a box filter. /// </summary> private void h2v1_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; for (int inrow = 0; inrow < m_cinfo.m_max_v_samp_factor; inrow++) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; outIndex < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[inrow][outIndex] = invalue; outIndex++; output_data[inrow][outIndex] = invalue; outIndex++; } } }
/// <summary> /// Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. /// /// The upsampling algorithm is linear interpolation between pixel centers, /// also known as a "triangle filter". This is a good compromise between /// speed and visual quality. The centers of the output pixels are 1/4 and 3/4 /// of the way between input pixel centers. /// /// A note about the "bias" calculations: when rounding fractional values to /// integer, we do not want to always round 0.5 up to the next integer. /// If we did that, we'd introduce a noticeable bias towards larger values. /// Instead, this code is arranged so that 0.5 will be rounded up or down at /// alternate pixel locations (a simple ordered dither pattern). /// </summary> private void h2v1_fancy_upsample(int downsampled_width, ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; for (int inrow = 0; inrow < m_cinfo.m_max_v_samp_factor; inrow++) { int row = m_upsampleRowOffset + inrow; int inIndex = 0; int outIndex = 0; /* Special case for first column */ int invalue = input_data[row][inIndex]; inIndex++; output_data[inrow][outIndex] = (byte)invalue; outIndex++; output_data[inrow][outIndex] = (byte)((invalue * 3 + (int)input_data[row][inIndex] + 2) >> 2); outIndex++; for (int colctr = downsampled_width - 2; colctr > 0; colctr--) { /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ invalue = (int)input_data[row][inIndex] * 3; inIndex++; output_data[inrow][outIndex] = (byte)((invalue + (int)input_data[row][inIndex - 2] + 1) >> 2); outIndex++; output_data[inrow][outIndex] = (byte)((invalue + (int)input_data[row][inIndex] + 2) >> 2); outIndex++; } /* Special case for last column */ invalue = input_data[row][inIndex]; output_data[inrow][outIndex] = (byte)((invalue * 3 + (int)input_data[row][inIndex - 1] + 1) >> 2); outIndex++; output_data[inrow][outIndex] = (byte)invalue; outIndex++; } }
/// <summary> /// Process some data. /// This handles the simple case where no context is required. /// </summary> private void process_data_simple_main(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { ComponentBuffer[] cb = new ComponentBuffer[JpegConstants.MAX_COMPONENTS]; for (int i = 0; i < JpegConstants.MAX_COMPONENTS; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(m_buffer[i], null, 0); } /* Read input data if we haven't filled the main buffer yet */ if (!m_buffer_full) { if (m_cinfo.m_coef.decompress_data(cb) == ReadResult.JPEG_SUSPENDED) { /* suspension forced, can do nothing more */ return; } /* OK, we have an iMCU row to work with */ m_buffer_full = true; } /* There are always min_DCT_scaled_size row groups in an iMCU row. */ int rowgroups_avail = m_cinfo.m_min_DCT_scaled_size; /* Note: at the bottom of the image, we may pass extra garbage row groups * to the postprocessor. The postprocessor has to check for bottom * of image anyway (at row resolution), so no point in us doing it too. */ /* Feed the postprocessor */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ if (m_rowgroup_ctr >= rowgroups_avail) { m_buffer_full = false; m_rowgroup_ctr = 0; } }
private void upsampleComponent(ref ComponentBuffer input_data) { switch (m_upsampleMethods[m_currentComponent]) { case ComponentUpsampler.noop_upsampler: noop_upsample(); break; case ComponentUpsampler.fullsize_upsampler: fullsize_upsample(ref input_data); break; case ComponentUpsampler.h2v1_fancy_upsampler: h2v1_fancy_upsample(m_cinfo.Comp_info[m_currentComponent].downsampled_width, ref input_data); break; case ComponentUpsampler.h2v1_upsampler: h2v1_upsample(ref input_data); break; case ComponentUpsampler.h2v2_fancy_upsampler: h2v2_fancy_upsample(m_cinfo.Comp_info[m_currentComponent].downsampled_width, ref input_data); break; case ComponentUpsampler.h2v2_upsampler: h2v2_upsample(ref input_data); break; case ComponentUpsampler.int_upsampler: int_upsample(ref input_data); break; default: m_cinfo.ERREXIT(J_MESSAGE_CODE.JERR_NOTIMPL); break; } }
/// <summary> /// Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. /// Again a triangle filter; see comments for h2v1 case, above. /// /// It is OK for us to reference the adjacent input rows because we demanded /// context from the main buffer controller (see initialization code). /// </summary> private void h2v2_fancy_upsample(int downsampled_width, ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int inrow = m_upsampleRowOffset; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { for (int v = 0; v < 2; v++) { // nearest input row index int inIndex0 = 0; //next nearest input row index int inIndex1 = 0; int inRow1 = -1; if (v == 0) { /* next nearest is row above */ inRow1 = inrow - 1; } else { /* next nearest is row below */ inRow1 = inrow + 1; } int row = outrow; int outIndex = 0; outrow++; /* Special case for first column */ int thiscolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; int nextcolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; output_data[row][outIndex] = (byte)((thiscolsum * 4 + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + nextcolsum + 7) >> 4); outIndex++; int lastcolsum = thiscolsum; thiscolsum = nextcolsum; for (int colctr = downsampled_width - 2; colctr > 0; colctr--) { /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ nextcolsum = (int)input_data[inrow][inIndex0] * 3 + (int)input_data[inRow1][inIndex1]; inIndex0++; inIndex1++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + lastcolsum + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 3 + nextcolsum + 7) >> 4); outIndex++; lastcolsum = thiscolsum; thiscolsum = nextcolsum; } /* Special case for last column */ output_data[row][outIndex] = (byte)((thiscolsum * 3 + lastcolsum + 8) >> 4); outIndex++; output_data[row][outIndex] = (byte)((thiscolsum * 4 + 7) >> 4); outIndex++; } inrow++; } }
/// <summary> /// Decompress and return some data in the multi-pass case. /// Always attempts to emit one fully interleaved MCU row ("iMCU" row). /// Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. /// /// NB: output_buf contains a plane for each component in image. /// </summary> private ReadResult decompress_data_ordinary(ComponentBuffer[] output_buf) { /* Force some input to be done if we are getting ahead of the input. */ while (m_cinfo.m_input_scan_number < m_cinfo.m_output_scan_number || (m_cinfo.m_input_scan_number == m_cinfo.m_output_scan_number && m_cinfo.m_input_iMCU_row <= m_cinfo.m_output_iMCU_row)) { if (m_cinfo.m_inputctl.consume_input() == ReadResult.JPEG_SUSPENDED) return ReadResult.JPEG_SUSPENDED; } int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* OK, output from the virtual arrays. */ for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { jpeg_component_info componentInfo = m_cinfo.Comp_info[ci]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) continue; /* Align the virtual buffer for this component. */ JBLOCK[][] buffer = m_whole_image[ci].Access(m_cinfo.m_output_iMCU_row * componentInfo.V_samp_factor, componentInfo.V_samp_factor); /* Count non-dummy DCT block rows in this iMCU row. */ int block_rows; if (m_cinfo.m_output_iMCU_row < last_iMCU_row) block_rows = componentInfo.V_samp_factor; else { /* NB: can't use last_row_height here; it is input-side-dependent! */ block_rows = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (block_rows == 0) block_rows = componentInfo.V_samp_factor; } /* Loop over all DCT blocks to be processed. */ int rowIndex = 0; for (int block_row = 0; block_row < block_rows; block_row++) { int output_col = 0; for (int block_num = 0; block_num < componentInfo.Width_in_blocks; block_num++) { m_cinfo.m_idct.inverse(componentInfo.Component_index, buffer[block_row][block_num].data, output_buf[ci], rowIndex, output_col); output_col += componentInfo.DCT_scaled_size; } rowIndex += componentInfo.DCT_scaled_size; } } m_cinfo.m_output_iMCU_row++; if (m_cinfo.m_output_iMCU_row < m_cinfo.m_total_iMCU_rows) return ReadResult.JPEG_ROW_COMPLETED; return ReadResult.JPEG_SCAN_COMPLETED; }
/// <summary> /// Variant of decompress_data for use when doing block smoothing. /// </summary> private ReadResult decompress_smooth_data(ComponentBuffer[] output_buf) { /* Force some input to be done if we are getting ahead of the input. */ while (m_cinfo.m_input_scan_number <= m_cinfo.m_output_scan_number && !m_cinfo.m_inputctl.EOIReached()) { if (m_cinfo.m_input_scan_number == m_cinfo.m_output_scan_number) { /* If input is working on current scan, we ordinarily want it to * have completed the current row. But if input scan is DC, * we want it to keep one row ahead so that next block row's DC * values are up to date. */ int delta = (m_cinfo.m_Ss == 0) ? 1 : 0; if (m_cinfo.m_input_iMCU_row > m_cinfo.m_output_iMCU_row + delta) break; } if (m_cinfo.m_inputctl.consume_input() == ReadResult.JPEG_SUSPENDED) return ReadResult.JPEG_SUSPENDED; } int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* OK, output from the virtual arrays. */ for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { jpeg_component_info componentInfo = m_cinfo.Comp_info[ci]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) continue; int block_rows; int access_rows; bool last_row; /* Count non-dummy DCT block rows in this iMCU row. */ if (m_cinfo.m_output_iMCU_row < last_iMCU_row) { block_rows = componentInfo.V_samp_factor; access_rows = block_rows * 2; /* this and next iMCU row */ last_row = false; } else { /* NB: can't use last_row_height here; it is input-side-dependent! */ block_rows = componentInfo.height_in_blocks % componentInfo.V_samp_factor; if (block_rows == 0) block_rows = componentInfo.V_samp_factor; access_rows = block_rows; /* this iMCU row only */ last_row = true; } /* Align the virtual buffer for this component. */ JBLOCK[][] buffer = null; bool first_row; int bufferRowOffset = 0; if (m_cinfo.m_output_iMCU_row > 0) { access_rows += componentInfo.V_samp_factor; /* prior iMCU row too */ buffer = m_whole_image[ci].Access((m_cinfo.m_output_iMCU_row - 1) * componentInfo.V_samp_factor, access_rows); bufferRowOffset = componentInfo.V_samp_factor; /* point to current iMCU row */ first_row = false; } else { buffer = m_whole_image[ci].Access(0, access_rows); first_row = true; } /* Fetch component-dependent info */ int coefBitsOffset = ci * SAVED_COEFS; int Q00 = componentInfo.quant_table.quantval[0]; int Q01 = componentInfo.quant_table.quantval[Q01_POS]; int Q10 = componentInfo.quant_table.quantval[Q10_POS]; int Q20 = componentInfo.quant_table.quantval[Q20_POS]; int Q11 = componentInfo.quant_table.quantval[Q11_POS]; int Q02 = componentInfo.quant_table.quantval[Q02_POS]; int outputIndex = ci; /* Loop over all DCT blocks to be processed. */ for (int block_row = 0; block_row < block_rows; block_row++) { int bufferIndex = bufferRowOffset + block_row; int prev_block_row = 0; if (first_row && block_row == 0) prev_block_row = bufferIndex; else prev_block_row = bufferIndex - 1; int next_block_row = 0; if (last_row && block_row == block_rows - 1) next_block_row = bufferIndex; else next_block_row = bufferIndex + 1; /* We fetch the surrounding DC values using a sliding-register approach. * Initialize all nine here so as to do the right thing on narrow pics. */ int DC1 = buffer[prev_block_row][0][0]; int DC2 = DC1; int DC3 = DC1; int DC4 = buffer[bufferIndex][0][0]; int DC5 = DC4; int DC6 = DC4; int DC7 = buffer[next_block_row][0][0]; int DC8 = DC7; int DC9 = DC7; int output_col = 0; int last_block_column = componentInfo.Width_in_blocks - 1; for (int block_num = 0; block_num <= last_block_column; block_num++) { /* Fetch current DCT block into workspace so we can modify it. */ JBLOCK workspace = new JBLOCK(); Buffer.BlockCopy(buffer[bufferIndex][0].data, 0, workspace.data, 0, workspace.data.Length * sizeof(short)); /* Update DC values */ if (block_num < last_block_column) { DC3 = buffer[prev_block_row][1][0]; DC6 = buffer[bufferIndex][1][0]; DC9 = buffer[next_block_row][1][0]; } /* Compute coefficient estimates per K.8. * An estimate is applied only if coefficient is still zero, * and is not known to be fully accurate. */ /* AC01 */ int Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 1]; if (Al != 0 && workspace[1] == 0) { int pred; int num = 36 * Q00 * (DC4 - DC6); if (num >= 0) { pred = ((Q01 << 7) + num) / (Q01 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q01 << 7) - num) / (Q01 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[1] = (short) pred; } /* AC10 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 2]; if (Al != 0 && workspace[8] == 0) { int pred; int num = 36 * Q00 * (DC2 - DC8); if (num >= 0) { pred = ((Q10 << 7) + num) / (Q10 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q10 << 7) - num) / (Q10 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[8] = (short) pred; } /* AC20 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 3]; if (Al != 0 && workspace[16] == 0) { int pred; int num = 9 * Q00 * (DC2 + DC8 - 2 * DC5); if (num >= 0) { pred = ((Q20 << 7) + num) / (Q20 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q20 << 7) - num) / (Q20 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[16] = (short) pred; } /* AC11 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 4]; if (Al != 0 && workspace[9] == 0) { int pred; int num = 5 * Q00 * (DC1 - DC3 - DC7 + DC9); if (num >= 0) { pred = ((Q11 << 7) + num) / (Q11 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q11 << 7) - num) / (Q11 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[9] = (short) pred; } /* AC02 */ Al = m_coef_bits_latch[m_coef_bits_savedOffset + coefBitsOffset + 5]; if (Al != 0 && workspace[2] == 0) { int pred; int num = 9 * Q00 * (DC4 + DC6 - 2 * DC5); if (num >= 0) { pred = ((Q02 << 7) + num) / (Q02 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; } else { pred = ((Q02 << 7) - num) / (Q02 << 8); if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; pred = -pred; } workspace[2] = (short) pred; } /* OK, do the IDCT */ m_cinfo.m_idct.inverse(componentInfo.Component_index, workspace.data, output_buf[outputIndex], 0, output_col); /* Advance for next column */ DC1 = DC2; DC2 = DC3; DC4 = DC5; DC5 = DC6; DC7 = DC8; DC8 = DC9; bufferIndex++; prev_block_row++; next_block_row++; output_col += componentInfo.DCT_scaled_size; } outputIndex += componentInfo.DCT_scaled_size; } } m_cinfo.m_output_iMCU_row++; if (m_cinfo.m_output_iMCU_row < m_cinfo.m_total_iMCU_rows) return ReadResult.JPEG_ROW_COMPLETED; return ReadResult.JPEG_SCAN_COMPLETED; }
private void ycc_rgb_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { int y = input_buf[0][input_row + component0RowOffset][col]; int cb = input_buf[1][input_row + component1RowOffset][col]; int cr = input_buf[2][input_row + component2RowOffset][col]; /* Range-limiting is essential due to noise introduced by DCT losses. */ output_buf[output_row + row][columnOffset + JpegConstants.RGB_RED] = limit[limitOffset + y + m_Cr_r_tab[cr]]; output_buf[output_row + row][columnOffset + JpegConstants.RGB_GREEN] = limit[limitOffset + y + JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS)]; output_buf[output_row + row][columnOffset + JpegConstants.RGB_BLUE] = limit[limitOffset + y + m_Cb_b_tab[cb]]; columnOffset += JpegConstants.RGB_PIXELSIZE; } input_row++; } }
public my_upsampler(jpeg_decompress_struct cinfo) { m_cinfo = cinfo; m_need_context_rows = false; /* until we find out differently */ if (cinfo.m_CCIR601_sampling) /* this isn't supported */ { cinfo.ERREXIT(J_MESSAGE_CODE.JERR_CCIR601_NOTIMPL); } /* jpeg_d_main_controller doesn't support context rows when min_DCT_scaled_size = 1, * so don't ask for it. */ bool do_fancy = cinfo.m_do_fancy_upsampling && cinfo.m_min_DCT_scaled_size > 1; /* Verify we can handle the sampling factors, select per-component methods, * and create storage as needed. */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { jpeg_component_info componentInfo = cinfo.Comp_info[ci]; /* Compute size of an "input group" after IDCT scaling. This many samples * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. */ int h_in_group = (componentInfo.H_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int v_in_group = (componentInfo.V_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int h_out_group = cinfo.m_max_h_samp_factor; int v_out_group = cinfo.m_max_v_samp_factor; /* save for use later */ m_rowgroup_height[ci] = v_in_group; bool need_buffer = true; if (!componentInfo.component_needed) { /* Don't bother to upsample an uninteresting component. */ m_upsampleMethods[ci] = ComponentUpsampler.noop_upsampler; need_buffer = false; } else if (h_in_group == h_out_group && v_in_group == v_out_group) { /* Fullsize components can be processed without any work. */ m_upsampleMethods[ci] = ComponentUpsampler.fullsize_upsampler; need_buffer = false; } else if (h_in_group * 2 == h_out_group && v_in_group == v_out_group) { /* Special cases for 2h1v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) { m_upsampleMethods[ci] = ComponentUpsampler.h2v1_fancy_upsampler; } else { m_upsampleMethods[ci] = ComponentUpsampler.h2v1_upsampler; } } else if (h_in_group * 2 == h_out_group && v_in_group * 2 == v_out_group) { /* Special cases for 2h2v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_fancy_upsampler; m_need_context_rows = true; } else { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_upsampler; } } else if ((h_out_group % h_in_group) == 0 && (v_out_group % v_in_group) == 0) { /* Generic integral-factors upsampling method */ m_upsampleMethods[ci] = ComponentUpsampler.int_upsampler; m_h_expand[ci] = (byte)(h_out_group / h_in_group); m_v_expand[ci] = (byte)(v_out_group / v_in_group); } else { cinfo.ERREXIT(J_MESSAGE_CODE.JERR_FRACT_SAMPLE_NOTIMPL); } if (need_buffer) { ComponentBuffer cb = new ComponentBuffer(); cb.SetBuffer(jpeg_common_struct.AllocJpegSamples(JpegUtils.jround_up(cinfo.m_output_width, cinfo.m_max_h_samp_factor), cinfo.m_max_v_samp_factor), null, 0); m_color_buf[ci] = cb; } } }
public static void jcopy_sample_rows(ComponentBuffer input_array, int source_row, ComponentBuffer output_array, int dest_row, int num_rows, int num_cols) { for (int row = 0; row < num_rows; row++) Buffer.BlockCopy(input_array[source_row + row], 0, output_array[dest_row + row], 0, num_cols); }
public abstract void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail);
/// <summary> /// Process some data. /// This handles the case where context rows must be provided. /// </summary> private void process_data_context_main(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { ComponentBuffer[] cb = new ComponentBuffer[m_cinfo.m_num_components]; for (int i = 0; i < m_cinfo.m_num_components; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(m_buffer[i], m_funnyIndices[m_whichFunny][i], m_funnyOffsets[i]); } /* Read input data if we haven't filled the main buffer yet */ if (!m_buffer_full) { if (m_cinfo.m_coef.decompress_data(cb) == ReadResult.JPEG_SUSPENDED) { /* suspension forced, can do nothing more */ return; } /* OK, we have an iMCU row to work with */ m_buffer_full = true; /* count rows received */ m_iMCU_row_ctr++; } /* Postprocessor typically will not swallow all the input data it is handed * in one call (due to filling the output buffer first). Must be prepared * to exit and restart. This switch lets us keep track of how far we got. * Note that each case falls through to the next on successful completion. */ if (m_context_state == CTX_POSTPONED_ROW) { /* Call postprocessor using previously set pointers for postponed row */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } m_context_state = CTX_PREPARE_FOR_IMCU; if (out_row_ctr >= out_rows_avail) { /* Postprocessor exactly filled output buf */ return; } } if (m_context_state == CTX_PREPARE_FOR_IMCU) { /* Prepare to process first M-1 row groups of this iMCU row */ m_rowgroup_ctr = 0; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size - 1; /* Check for bottom of image: if so, tweak pointers to "duplicate" * the last sample row, and adjust rowgroups_avail to ignore padding rows. */ if (m_iMCU_row_ctr == m_cinfo.m_total_iMCU_rows) set_bottom_pointers(); m_context_state = CTX_PROCESS_IMCU; } if (m_context_state == CTX_PROCESS_IMCU) { /* Call postprocessor using previously set pointers */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } /* After the first iMCU, change wraparound pointers to normal state */ if (m_iMCU_row_ctr == 1) set_wraparound_pointers(); /* Prepare to load new iMCU row using other xbuffer list */ m_whichFunny ^= 1; /* 0=>1 or 1=>0 */ m_buffer_full = false; /* Still need to process last row group of this iMCU row, */ /* which is saved at index M+1 of the other xbuffer */ m_rowgroup_ctr = m_cinfo.m_min_DCT_scaled_size + 1; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size + 2; m_context_state = CTX_POSTPONED_ROW; } }
/// <summary> /// Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. /// </summary> private void h2v2_merged_upsample(ComponentBuffer[] input_buf, int in_row_group_ctr, byte[][] output_buf) { int inputRow00 = in_row_group_ctr * 2; int inputIndex00 = 0; int inputRow01 = in_row_group_ctr * 2 + 1; int inputIndex01 = 0; int inputIndex1 = 0; int inputIndex2 = 0; int outIndex0 = 0; int outIndex1 = 0; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; /* Loop for each group of output pixels */ for (int col = m_cinfo.m_output_width >> 1; col > 0; col--) { /* Do the chroma part of the calculation */ int cb = input_buf[1][in_row_group_ctr][inputIndex1]; inputIndex1++; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; inputIndex2++; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; /* Fetch 4 Y values and emit 4 pixels */ int y = input_buf[0][inputRow00][inputIndex00]; inputIndex00++; output_buf[0][outIndex0 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; outIndex0 += JpegConstants.RGB_PIXELSIZE; y = input_buf[0][inputRow00][inputIndex00]; inputIndex00++; output_buf[0][outIndex0 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; outIndex0 += JpegConstants.RGB_PIXELSIZE; y = input_buf[0][inputRow01][inputIndex01]; inputIndex01++; output_buf[1][outIndex1 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; outIndex1 += JpegConstants.RGB_PIXELSIZE; y = input_buf[0][inputRow01][inputIndex01]; inputIndex01++; output_buf[1][outIndex1 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; outIndex1 += JpegConstants.RGB_PIXELSIZE; } /* If image width is odd, do the last output column separately */ if ((m_cinfo.m_output_width & 1) != 0) { int cb = input_buf[1][in_row_group_ctr][inputIndex1]; int cr = input_buf[2][in_row_group_ctr][inputIndex2]; int cred = m_Cr_r_tab[cr]; int cgreen = JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS); int cblue = m_Cb_b_tab[cb]; int y = input_buf[0][inputRow00][inputIndex00]; output_buf[0][outIndex0 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[0][outIndex0 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[0][outIndex0 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; y = input_buf[0][inputRow01][inputIndex01]; output_buf[1][outIndex1 + JpegConstants.RGB_RED] = limit[limitOffset + y + cred]; output_buf[1][outIndex1 + JpegConstants.RGB_GREEN] = limit[limitOffset + y + cgreen]; output_buf[1][outIndex1 + JpegConstants.RGB_BLUE] = limit[limitOffset + y + cblue]; } }
/// <summary> /// Control routine to do upsampling (and color conversion). /// The control routine just handles the row buffering considerations. /// 2:1 vertical sampling case: may need a spare row. /// </summary> private void merged_2v_upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { int num_rows; /* number of rows returned to caller */ if (m_spare_full) { /* If we have a spare row saved from a previous cycle, just return it. */ byte[][] temp = new byte[1][]; temp[0] = m_spare_row; JpegUtils.jcopy_sample_rows(temp, 0, output_buf, out_row_ctr, 1, m_out_row_width); num_rows = 1; m_spare_full = false; } else { /* Figure number of rows to return to caller. */ num_rows = 2; /* Not more than the distance to the end of the image. */ if (num_rows > m_rows_to_go) num_rows = m_rows_to_go; /* And not more than what the client can accept: */ out_rows_avail -= out_row_ctr; if (num_rows > out_rows_avail) num_rows = out_rows_avail; /* Create output pointer array for upsampler. */ byte[][] work_ptrs = new byte[2][]; work_ptrs[0] = output_buf[out_row_ctr]; if (num_rows > 1) { work_ptrs[1] = output_buf[out_row_ctr + 1]; } else { work_ptrs[1] = m_spare_row; m_spare_full = true; } /* Now do the upsampling. */ h2v2_merged_upsample(input_buf, in_row_group_ctr, work_ptrs); } /* Adjust counts */ out_row_ctr += num_rows; m_rows_to_go -= num_rows; /* When the buffer is emptied, declare this input row group consumed */ if (!m_spare_full) in_row_group_ctr++; }
/// <summary> /// Control routine to do upsampling (and color conversion). /// The control routine just handles the row buffering considerations. /// 1:1 vertical sampling case: much easier, never need a spare row. /// </summary> private void merged_1v_upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, byte[][] output_buf, ref int out_row_ctr) { /* Just do the upsampling. */ h2v1_merged_upsample(input_buf, in_row_group_ctr, output_buf, out_row_ctr); /* Adjust counts */ out_row_ctr++; in_row_group_ctr++; }
/// <summary> /// Alternate entry point to read raw data. /// </summary> /// <param name="data">The raw data.</param> /// <param name="max_lines">The number of scanlines for reading.</param> /// <returns>The number of lines actually read.</returns> /// <remarks>Replaces <see cref="jpeg_decompress_struct.jpeg_read_scanlines">jpeg_read_scanlines</see> /// when reading raw downsampled data. Processes exactly one iMCU row per call, unless suspended. /// </remarks> public int jpeg_read_raw_data(byte[][][] data, int max_lines) { if (m_global_state != JpegState.DSTATE_RAW_OK) ERREXIT(J_MESSAGE_CODE.JERR_BAD_STATE, (int)m_global_state); if (m_output_scanline >= m_output_height) { WARNMS(J_MESSAGE_CODE.JWRN_TOO_MUCH_DATA); return 0; } /* Call progress monitor hook if present */ if (m_progress != null) { m_progress.Pass_counter = m_output_scanline; m_progress.Pass_limit = m_output_height; m_progress.Updated(); } /* Verify that at least one iMCU row can be returned. */ int lines_per_iMCU_row = m_max_v_samp_factor * m_min_DCT_scaled_size; if (max_lines < lines_per_iMCU_row) ERREXIT(J_MESSAGE_CODE.JERR_BUFFER_SIZE); int componentCount = data.Length; // maybe we should use max_lines here ComponentBuffer[] cb = new ComponentBuffer[componentCount]; for (int i = 0; i < componentCount; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(data[i], null, 0); } /* Decompress directly into user's buffer. */ if (m_coef.decompress_data(cb) == ReadResult.JPEG_SUSPENDED) { /* suspension forced, can do nothing more */ return 0; } /* OK, we processed one iMCU row. */ m_output_scanline += lines_per_iMCU_row; return lines_per_iMCU_row; }
public override void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { if (m_use_2v_upsample) merged_2v_upsample(input_buf, ref in_row_group_ctr, output_buf, ref out_row_ctr, out_rows_avail); else merged_1v_upsample(input_buf, ref in_row_group_ctr, output_buf, ref out_row_ctr); }
/* Inverse DCT (also performs dequantization) */ public void inverse(int component_index, short[] coef_block, ComponentBuffer output_buf, int output_row, int output_col) { m_componentBuffer = output_buf; switch (m_inverse_DCT_method[component_index]) { case InverseMethod.idct_1x1_method: jpeg_idct_1x1(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_2x2_method: jpeg_idct_2x2(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_4x4_method: jpeg_idct_4x4(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_islow_method: jpeg_idct_islow(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_ifast_method: jpeg_idct_ifast(component_index, coef_block, output_row, output_col); break; case InverseMethod.idct_float_method: jpeg_idct_float(component_index, coef_block, output_row, output_col); break; case InverseMethod.Unknown: default: m_cinfo.ERREXIT(J_MESSAGE_CODE.JERR_NOT_COMPILED); break; } }
/// <summary> /// Convert some rows of samples to the output colorspace. /// /// Note that we change from noninterleaved, one-plane-per-component format /// to interleaved-pixel format. The output buffer is therefore three times /// as wide as the input buffer. /// A starting row offset is provided only for the input buffer. The caller /// can easily adjust the passed output_buf value to accommodate any row /// offset required on that side. /// </summary> public void color_convert(ComponentBuffer[] input_buf, int[] perComponentOffsets, int input_row, byte[][] output_buf, int output_row, int num_rows) { m_perComponentOffsets = perComponentOffsets; switch (m_converter) { case ColorConverter.grayscale_converter: grayscale_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.ycc_rgb_converter: ycc_rgb_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.gray_rgb_converter: gray_rgb_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.null_converter: null_convert(input_buf, input_row, output_buf, output_row, num_rows); break; case ColorConverter.ycck_cmyk_converter: ycck_cmyk_convert(input_buf, input_row, output_buf, output_row, num_rows); break; default: m_cinfo.ERREXIT(J_MESSAGE_CODE.JERR_CONVERSION_NOTIMPL); break; } }
/// <summary> /// Color conversion for grayscale: just copy the data. /// This also works for YCbCr -> grayscale conversion, in which /// we just copy the Y (luminance) component and ignore chrominance. /// </summary> private void grayscale_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { JpegUtils.jcopy_sample_rows(input_buf[0], input_row + m_perComponentOffsets[0], output_buf, output_row, num_rows, m_cinfo.m_output_width); }
public ReadResult decompress_data(ComponentBuffer[] output_buf) { switch (m_decompressor) { case DecompressorType.Ordinary: return decompress_data_ordinary(output_buf); case DecompressorType.Smooth: return decompress_smooth_data(output_buf); case DecompressorType.OnePass: return decompress_onepass(output_buf); } m_cinfo.ERREXIT(J_MESSAGE_CODE.JERR_NOTIMPL); return 0; }
/// <summary> /// Process some data. /// This handles the case where context rows must be provided. /// </summary> private void process_data_context_main(byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { ComponentBuffer[] cb = new ComponentBuffer[m_cinfo.m_num_components]; for (int i = 0; i < m_cinfo.m_num_components; i++) { cb[i] = new ComponentBuffer(); cb[i].SetBuffer(m_buffer[i], m_funnyIndices[m_whichFunny][i], m_funnyOffsets[i]); } /* Read input data if we haven't filled the main buffer yet */ if (!m_buffer_full) { if (m_cinfo.m_coef.decompress_data(cb) == ReadResult.JPEG_SUSPENDED) { /* suspension forced, can do nothing more */ return; } /* OK, we have an iMCU row to work with */ m_buffer_full = true; /* count rows received */ m_iMCU_row_ctr++; } /* Postprocessor typically will not swallow all the input data it is handed * in one call (due to filling the output buffer first). Must be prepared * to exit and restart. * * * This switch lets us keep track of how far we got. * Note that each case falls through to the next on successful completion. */ if (m_context_state == CTX_POSTPONED_ROW) { /* Call postprocessor using previously set pointers for postponed row */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } m_context_state = CTX_PREPARE_FOR_IMCU; if (out_row_ctr >= out_rows_avail) { /* Postprocessor exactly filled output buf */ return; } } if (m_context_state == CTX_PREPARE_FOR_IMCU) { /* Prepare to process first M-1 row groups of this iMCU row */ m_rowgroup_ctr = 0; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size - 1; /* Check for bottom of image: if so, tweak pointers to "duplicate" * the last sample row, and adjust rowgroups_avail to ignore padding rows. */ if (m_iMCU_row_ctr == m_cinfo.m_total_iMCU_rows) { set_bottom_pointers(); } m_context_state = CTX_PROCESS_IMCU; } if (m_context_state == CTX_PROCESS_IMCU) { /* Call postprocessor using previously set pointers */ m_cinfo.m_post.post_process_data(cb, ref m_rowgroup_ctr, m_rowgroups_avail, output_buf, ref out_row_ctr, out_rows_avail); if (m_rowgroup_ctr < m_rowgroups_avail) { /* Need to suspend */ return; } /* After the first iMCU, change wraparound pointers to normal state */ if (m_iMCU_row_ctr == 1) { set_wraparound_pointers(); } /* Prepare to load new iMCU row using other xbuffer list */ m_whichFunny ^= 1; /* 0=>1 or 1=>0 */ m_buffer_full = false; /* Still need to process last row group of this iMCU row, */ /* which is saved at index M+1 of the other xbuffer */ m_rowgroup_ctr = m_cinfo.m_min_DCT_scaled_size + 1; m_rowgroups_avail = m_cinfo.m_min_DCT_scaled_size + 2; m_context_state = CTX_POSTPONED_ROW; } }
/// <summary> /// Decompress and return some data in the single-pass case. /// Always attempts to emit one fully interleaved MCU row ("iMCU" row). /// Input and output must run in lockstep since we have only a one-MCU buffer. /// Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. /// /// NB: output_buf contains a plane for each component in image, /// which we index according to the component's SOF position. /// </summary> private ReadResult decompress_onepass(ComponentBuffer[] output_buf) { int last_MCU_col = m_cinfo.m_MCUs_per_row - 1; int last_iMCU_row = m_cinfo.m_total_iMCU_rows - 1; /* Loop to process as much as one whole iMCU row */ for (int yoffset = m_MCU_vert_offset; yoffset < m_MCU_rows_per_iMCU_row; yoffset++) { for (int MCU_col_num = m_MCU_ctr; MCU_col_num <= last_MCU_col; MCU_col_num++) { /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ for (int i = 0; i < m_cinfo.m_blocks_in_MCU; i++) Array.Clear(m_MCU_buffer[i].data, 0, m_MCU_buffer[i].data.Length); if (!m_cinfo.m_entropy.decode_mcu(m_MCU_buffer)) { /* Suspension forced; update state counters and exit */ m_MCU_vert_offset = yoffset; m_MCU_ctr = MCU_col_num; return ReadResult.JPEG_SUSPENDED; } /* Determine where data should go in output_buf and do the IDCT thing. * We skip dummy blocks at the right and bottom edges (but blkn gets * incremented past them!). Note the inner loop relies on having * allocated the MCU_buffer[] blocks sequentially. */ int blkn = 0; /* index of current DCT block within MCU */ for (int ci = 0; ci < m_cinfo.m_comps_in_scan; ci++) { jpeg_component_info componentInfo = m_cinfo.Comp_info[m_cinfo.m_cur_comp_info[ci]]; /* Don't bother to IDCT an uninteresting component. */ if (!componentInfo.component_needed) { blkn += componentInfo.MCU_blocks; continue; } int useful_width = (MCU_col_num < last_MCU_col) ? componentInfo.MCU_width : componentInfo.last_col_width; int outputIndex = yoffset * componentInfo.DCT_scaled_size; int start_col = MCU_col_num * componentInfo.MCU_sample_width; for (int yindex = 0; yindex < componentInfo.MCU_height; yindex++) { if (m_cinfo.m_input_iMCU_row < last_iMCU_row || yoffset + yindex < componentInfo.last_row_height) { int output_col = start_col; for (int xindex = 0; xindex < useful_width; xindex++) { m_cinfo.m_idct.inverse(componentInfo.Component_index, m_MCU_buffer[blkn + xindex].data, output_buf[componentInfo.Component_index], outputIndex, output_col); output_col += componentInfo.DCT_scaled_size; } } blkn += componentInfo.MCU_width; outputIndex += componentInfo.DCT_scaled_size; } } } /* Completed an MCU row, but perhaps not an iMCU row */ m_MCU_ctr = 0; } /* Completed the iMCU row, advance counters for next one */ m_cinfo.m_output_iMCU_row++; m_cinfo.m_input_iMCU_row++; if (m_cinfo.m_input_iMCU_row < m_cinfo.m_total_iMCU_rows) { start_iMCU_row(); return ReadResult.JPEG_ROW_COMPLETED; } /* Completed the scan */ m_cinfo.m_inputctl.finish_input_pass(); return ReadResult.JPEG_SCAN_COMPLETED; }
/// <summary> /// Process some data in the first pass of 2-pass quantization. /// </summary> private void post_process_prepass(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, ref int out_row_ctr) { int old_next_row, num_rows; /* Reposition virtual buffer if at start of strip. */ if (m_next_row == 0) m_buffer = m_whole_image.Access(m_starting_row, m_strip_height); /* Upsample some data (up to a strip height's worth). */ old_next_row = m_next_row; m_cinfo.m_upsample.upsample(input_buf, ref in_row_group_ctr, in_row_groups_avail, m_buffer, ref m_next_row, m_strip_height); /* Allow quantizer to scan new data. No data is emitted, */ /* but we advance out_row_ctr so outer loop can tell when we're done. */ if (m_next_row > old_next_row) { num_rows = m_next_row - old_next_row; m_cinfo.m_cquantize.color_quantize(m_buffer, old_next_row, null, 0, num_rows); out_row_ctr += num_rows; } /* Advance if we filled the strip. */ if (m_next_row >= m_strip_height) { m_starting_row += m_strip_height; m_next_row = 0; } }
/// <summary> /// Control routine to do upsampling (and color conversion). /// /// In this version we upsample each component independently. /// We upsample one row group into the conversion buffer, then apply /// color conversion a row at a time. /// </summary> public override void upsample(ComponentBuffer[] input_buf, ref int in_row_group_ctr, int in_row_groups_avail, byte[][] output_buf, ref int out_row_ctr, int out_rows_avail) { /* Fill the conversion buffer, if it's empty */ if (m_next_row_out >= m_cinfo.m_max_v_samp_factor) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { m_perComponentOffsets[ci] = 0; /* Invoke per-component upsample method.*/ m_currentComponent = ci; m_upsampleRowOffset = in_row_group_ctr * m_rowgroup_height[ci]; upsampleComponent(ref input_buf[ci]); } m_next_row_out = 0; } /* Color-convert and emit rows */ /* How many we have in the buffer: */ int num_rows = m_cinfo.m_max_v_samp_factor - m_next_row_out; /* Not more than the distance to the end of the image. Need this test * in case the image height is not a multiple of max_v_samp_factor: */ if (num_rows > m_rows_to_go) num_rows = m_rows_to_go; /* And not more than what the client can accept: */ out_rows_avail -= out_row_ctr; if (num_rows > out_rows_avail) num_rows = out_rows_avail; m_cinfo.m_cconvert.color_convert(m_color_buf, m_perComponentOffsets, m_next_row_out, output_buf, out_row_ctr, num_rows); /* Adjust counts */ out_row_ctr += num_rows; m_rows_to_go -= num_rows; m_next_row_out += num_rows; /* When the buffer is emptied, declare this input row group consumed */ if (m_next_row_out >= m_cinfo.m_max_v_samp_factor) in_row_group_ctr++; }
/// <summary> /// For full-size components, we just make color_buf[ci] point at the /// input buffer, and thus avoid copying any data. Note that this is /// safe only because sep_upsample doesn't declare the input row group /// "consumed" until we are done color converting and emitting it. /// </summary> private void fullsize_upsample(ref ComponentBuffer input_data) { m_color_buf[m_currentComponent] = input_data; m_perComponentOffsets[m_currentComponent] = m_upsampleRowOffset; }
/// <summary> /// Convert grayscale to RGB: just duplicate the graylevel three times. /// This is provided to support applications that don't want to cope /// with grayscale as a separate case. /// </summary> private void gray_rgb_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; int num_cols = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { /* We can dispense with GETJSAMPLE() here */ output_buf[output_row + row][columnOffset + JpegConstants.RGB_RED] = input_buf[0][input_row + component0RowOffset][col]; output_buf[output_row + row][columnOffset + JpegConstants.RGB_GREEN] = input_buf[0][input_row + component1RowOffset][col]; output_buf[output_row + row][columnOffset + JpegConstants.RGB_BLUE] = input_buf[0][input_row + component2RowOffset][col]; columnOffset += JpegConstants.RGB_PIXELSIZE; } input_row++; } }
/// <summary> /// Fast processing for the common case of 2:1 horizontal and 1:1 vertical. /// It's still a box filter. /// </summary> private void h2v1_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; for (int inrow = 0; inrow < m_cinfo.m_max_v_samp_factor; inrow++) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[inrow][outIndex] = invalue; outIndex++; output_data[inrow][outIndex] = invalue; outIndex++; } } }
/**************** Cases other than YCbCr -> RGB **************/ /// <summary> /// Adobe-style YCCK->CMYK conversion. /// We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same /// conversion as above, while passing K (black) unchanged. /// We assume build_ycc_rgb_table has been called. /// </summary> private void ycck_cmyk_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { int component0RowOffset = m_perComponentOffsets[0]; int component1RowOffset = m_perComponentOffsets[1]; int component2RowOffset = m_perComponentOffsets[2]; int component3RowOffset = m_perComponentOffsets[3]; byte[] limit = m_cinfo.m_sample_range_limit; int limitOffset = m_cinfo.m_sampleRangeLimitOffset; int num_cols = m_cinfo.m_output_width; for (int row = 0; row < num_rows; row++) { int columnOffset = 0; for (int col = 0; col < num_cols; col++) { int y = input_buf[0][input_row + component0RowOffset][col]; int cb = input_buf[1][input_row + component1RowOffset][col]; int cr = input_buf[2][input_row + component2RowOffset][col]; /* Range-limiting is essential due to noise introduced by DCT losses. */ output_buf[output_row + row][columnOffset] = limit[limitOffset + JpegConstants.MAXJSAMPLE - (y + m_Cr_r_tab[cr])]; /* red */ output_buf[output_row + row][columnOffset + 1] = limit[limitOffset + JpegConstants.MAXJSAMPLE - (y + JpegUtils.RIGHT_SHIFT(m_Cb_g_tab[cb] + m_Cr_g_tab[cr], SCALEBITS))]; /* green */ output_buf[output_row + row][columnOffset + 2] = limit[limitOffset + JpegConstants.MAXJSAMPLE - (y + m_Cb_b_tab[cb])]; /* blue */ /* K passes through unchanged */ /* don't need GETJSAMPLE here */ output_buf[output_row + row][columnOffset + 3] = input_buf[3][input_row + component3RowOffset][col]; columnOffset += 4; } input_row++; } }
/// <summary> /// Fast processing for the common case of 2:1 horizontal and 2:1 vertical. /// It's still a box filter. /// </summary> private void h2v2_upsample(ref ComponentBuffer input_data) { ComponentBuffer output_data = m_color_buf[m_currentComponent]; int inrow = 0; int outrow = 0; while (outrow < m_cinfo.m_max_v_samp_factor) { int row = m_upsampleRowOffset + inrow; int outIndex = 0; for (int col = 0; col < m_cinfo.m_output_width; col++) { byte invalue = input_data[row][col]; /* don't need GETJSAMPLE() here */ output_data[outrow][outIndex] = invalue; outIndex++; output_data[outrow][outIndex] = invalue; outIndex++; } JpegUtils.jcopy_sample_rows(output_data, outrow, output_data, outrow + 1, 1, m_cinfo.m_output_width); inrow++; outrow += 2; } }
public my_upsampler(jpeg_decompress_struct cinfo) { m_cinfo = cinfo; m_need_context_rows = false; /* until we find out differently */ if (cinfo.m_CCIR601_sampling) /* this isn't supported */ cinfo.ERREXIT(J_MESSAGE_CODE.JERR_CCIR601_NOTIMPL); /* jpeg_d_main_controller doesn't support context rows when min_DCT_scaled_size = 1, * so don't ask for it. */ bool do_fancy = cinfo.m_do_fancy_upsampling && cinfo.m_min_DCT_scaled_size > 1; /* Verify we can handle the sampling factors, select per-component methods, * and create storage as needed. */ for (int ci = 0; ci < cinfo.m_num_components; ci++) { jpeg_component_info componentInfo = cinfo.Comp_info[ci]; /* Compute size of an "input group" after IDCT scaling. This many samples * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. */ int h_in_group = (componentInfo.H_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int v_in_group = (componentInfo.V_samp_factor * componentInfo.DCT_scaled_size) / cinfo.m_min_DCT_scaled_size; int h_out_group = cinfo.m_max_h_samp_factor; int v_out_group = cinfo.m_max_v_samp_factor; /* save for use later */ m_rowgroup_height[ci] = v_in_group; bool need_buffer = true; if (!componentInfo.component_needed) { /* Don't bother to upsample an uninteresting component. */ m_upsampleMethods[ci] = ComponentUpsampler.noop_upsampler; need_buffer = false; } else if (h_in_group == h_out_group && v_in_group == v_out_group) { /* Fullsize components can be processed without any work. */ m_upsampleMethods[ci] = ComponentUpsampler.fullsize_upsampler; need_buffer = false; } else if (h_in_group * 2 == h_out_group && v_in_group == v_out_group) { /* Special cases for 2h1v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) m_upsampleMethods[ci] = ComponentUpsampler.h2v1_fancy_upsampler; else m_upsampleMethods[ci] = ComponentUpsampler.h2v1_upsampler; } else if (h_in_group * 2 == h_out_group && v_in_group * 2 == v_out_group) { /* Special cases for 2h2v upsampling */ if (do_fancy && componentInfo.downsampled_width > 2) { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_fancy_upsampler; m_need_context_rows = true; } else { m_upsampleMethods[ci] = ComponentUpsampler.h2v2_upsampler; } } else if ((h_out_group % h_in_group) == 0 && (v_out_group % v_in_group) == 0) { /* Generic integral-factors upsampling method */ m_upsampleMethods[ci] = ComponentUpsampler.int_upsampler; m_h_expand[ci] = (byte) (h_out_group / h_in_group); m_v_expand[ci] = (byte) (v_out_group / v_in_group); } else cinfo.ERREXIT(J_MESSAGE_CODE.JERR_FRACT_SAMPLE_NOTIMPL); if (need_buffer) { ComponentBuffer cb = new ComponentBuffer(); cb.SetBuffer(jpeg_common_struct.AllocJpegSamples(JpegUtils.jround_up(cinfo.m_output_width, cinfo.m_max_h_samp_factor), cinfo.m_max_v_samp_factor), null, 0); m_color_buf[ci] = cb; } } }
/// <summary> /// Color conversion for no colorspace change: just copy the data, /// converting from separate-planes to interleaved representation. /// </summary> private void null_convert(ComponentBuffer[] input_buf, int input_row, byte[][] output_buf, int output_row, int num_rows) { for (int row = 0; row < num_rows; row++) { for (int ci = 0; ci < m_cinfo.m_num_components; ci++) { int columnIndex = 0; int componentOffset = 0; int perComponentOffset = m_perComponentOffsets[ci]; for (int count = m_cinfo.m_output_width; count > 0; count--) { /* needn't bother with GETJSAMPLE() here */ output_buf[output_row + row][ci + componentOffset] = input_buf[ci][input_row + perComponentOffset][columnIndex]; componentOffset += m_cinfo.m_num_components; columnIndex++; } } input_row++; } }