// Create the color index table. static void create_colorindex(jpeg_decompress cinfo) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; cquantize.colorindex = alloc_sarray(cinfo, (uint)(MAXJSAMPLE + 1), (uint)cinfo.out_color_components); // blksize is number of adjacent repeated entries for a component int blksize = cquantize.sv_actual; for (int i = 0; i < cinfo.out_color_components; i++) { // fill in colorindex entries for i'th color component int nci = cquantize.Ncolors[i]; // # of distinct values for this color blksize = blksize / nci; // in loop, val = index of current output value, // and k = largest j that maps to current val byte[] indexptr = cquantize.colorindex[i]; int val = 0; int k = largest_input_value(cinfo, i, 0, nci - 1); for (int j = 0; j <= MAXJSAMPLE; j++) { while (j > k) { k = largest_input_value(cinfo, i, ++val, nci - 1); // advance val if past boundary } // premultiply so that no multiplication needed in main processing indexptr[j] = (byte)(val * blksize); } } }
// Create the colormap. static void create_colormap(jpeg_decompress cinfo) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; // Select number of colors for each component int total_colors = select_ncolors(cinfo, cquantize.Ncolors); // Number of distinct output colors // Report selected color counts if (cinfo.out_color_components == 3) { TRACEMS4(cinfo, 1, J_MESSAGE_CODE.JTRC_QUANT_3_NCOLORS, total_colors, cquantize.Ncolors[0], cquantize.Ncolors[1], cquantize.Ncolors[2]); } else { TRACEMS1(cinfo, 1, J_MESSAGE_CODE.JTRC_QUANT_NCOLORS, total_colors); } // Allocate and fill in the colormap. // The colors are ordered in the map in standard row-major order, // i.e. rightmost (highest-indexed) color changes most rapidly. byte[][] colormap = alloc_sarray(cinfo, (uint)total_colors, (uint)cinfo.out_color_components); // blksize is number of adjacent repeated entries for a component // blkdist is distance between groups of identical entries for a component int blkdist = total_colors; for (int i = 0; i < cinfo.out_color_components; i++) { // fill in colormap entries for i'th color component int nci = cquantize.Ncolors[i]; // # of distinct values for this color int blksize = blkdist / nci; for (int j = 0; j < nci; j++) { // Compute j'th output value (out of nci) for component int val = output_value(cinfo, i, j, nci - 1); // Fill in all colormap entries that have this value of this component for (int ptr = j * blksize; ptr < total_colors; ptr += blkdist) { // fill in blksize entries beginning at ptr for (int k = 0; k < blksize; k++) { colormap[i][ptr + k] = (byte)val; } } } blkdist = blksize; // blksize of this color is blkdist of next } // Save the colormap in private storage, // where it will survive color quantization mode changes. cquantize.sv_colormap = colormap; cquantize.sv_actual = total_colors; }
// General case, with ordered dithering static void quantize_ord_dither(jpeg_decompress cinfo, byte[][] input_buf, uint input_row, byte[][] output_buf, uint output_row, int num_rows) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; int row_index; // current indexes into dither matrix int nc = cinfo.out_color_components; uint width = cinfo.output_width; for (int row = 0; row < num_rows; row++) { // Initialize output values to 0 so can process components separately for (int i = 0; i < width; i++) { output_buf[output_row + row][i] = 0; } row_index = cquantize.row_index; for (int ci = 0; ci < nc; ci++) { byte[] input_ptr = input_buf[input_row + row]; int iind = ci; byte[] output_ptr = output_buf[output_row + row]; uint oind = 0; byte[] colorindex_ci = cquantize.colorindex[ci]; int[,] dither = cquantize.odither[ci]; // points to active row of dither matrix int col_index = 0; for (uint col = width; col > 0; col--) { // Form pixel value + dither, range-limit to 0..MAXJSAMPLE, // select output value, accumulate into output code for this pixel. // Range-limiting need not be done explicitly, as we have extended // the colorindex table to produce the right answers for out-of-range // inputs. The maximum dither is +- MAXJSAMPLE; this sets the // required amount of padding. int tmp = input_ptr[iind] + dither[row_index, col_index]; output_ptr[oind] += colorindex_ci[(tmp >= MAXJSAMPLE?MAXJSAMPLE:(tmp < 0?0:tmp))]; iind += nc; oind++; col_index = (col_index + 1) & ODITHER_MASK; } } // Advance row index for next row row_index = (row_index + 1) & ODITHER_MASK; cquantize.row_index = row_index; } }
// Allocate workspace for Floyd-Steinberg errors. static void alloc_fs_workspace(jpeg_decompress cinfo) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; try { uint arraysize = cinfo.output_width + 2; for (int i = 0; i < cinfo.out_color_components; i++) { cquantize.fserrors[i] = new int[arraysize]; } } catch { ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_OUT_OF_MEMORY, 4); } }
// Module initialization routine for 1-pass color quantization. public static void jinit_1pass_quantizer(jpeg_decompress cinfo) { my_cquantizer1 cquantize = null; try { cquantize = new my_cquantizer1(); } catch { ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_OUT_OF_MEMORY, 4); } cinfo.cquantize = cquantize; cquantize.start_pass = start_pass_1_quant; cquantize.finish_pass = finish_pass_1_quant; cquantize.new_color_map = new_color_map_1_quant; cquantize.fserrors[0] = null; // Flag FS workspace not allocated cquantize.odither[0] = null; // Also flag odither arrays not allocated // Make sure my internal arrays won't overflow if (cinfo.out_color_components > MAX_Q_COMPS) { ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_QUANT_COMPONENTS, MAX_Q_COMPS); } // Make sure colormap indexes can be represented by bytes if (cinfo.desired_number_of_colors > (MAXJSAMPLE + 1)) { ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_QUANT_MANY_COLORS, MAXJSAMPLE + 1); } // Create the colormap and color index table. create_colormap(cinfo); create_colorindex(cinfo); // Allocate Floyd-Steinberg workspace now if requested. // We do this now since it is storage and may effect the memory // manager's space calculations. If the user changes to FS dither // mode in a later pass, we will allocate the space then, and will // possibly overrun the max_memory_to_use setting. if (cinfo.dither_mode == J_DITHER_MODE.JDITHER_FS) { alloc_fs_workspace(cinfo); } }
// Fast path for out_color_components==3, with ordered dithering static void quantize3_ord_dither(jpeg_decompress cinfo, byte[][] input_buf, uint input_row, byte[][] output_buf, uint output_row, int num_rows) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; byte[] colorindex0 = cquantize.colorindex[0]; byte[] colorindex1 = cquantize.colorindex[1]; byte[] colorindex2 = cquantize.colorindex[2]; uint width = cinfo.output_width; for (int row = 0; row < num_rows; row++) { int row_index = cquantize.row_index; byte[] input_ptr = input_buf[input_row + row]; byte[] output_ptr = output_buf[output_row + row]; uint iind = 0, oind = 0; int[,] dither0 = cquantize.odither[0]; // points to active row of dither matrix int[,] dither1 = cquantize.odither[1]; int[,] dither2 = cquantize.odither[2]; int col_index = 0; for (uint col = width; col > 0; col--) { int tmp = input_ptr[iind++] + dither0[row_index, col_index]; int pixcode = colorindex0[(tmp >= MAXJSAMPLE?MAXJSAMPLE:(tmp < 0?0:tmp))]; tmp = input_ptr[iind++] + dither1[row_index, col_index]; pixcode += colorindex1[(tmp >= MAXJSAMPLE?MAXJSAMPLE:(tmp < 0?0:tmp))]; tmp = input_ptr[iind++] + dither2[row_index, col_index]; pixcode += colorindex2[(tmp >= MAXJSAMPLE?MAXJSAMPLE:(tmp < 0?0:tmp))]; output_ptr[oind++] = (byte)pixcode; col_index = (col_index + 1) & ODITHER_MASK; } row_index = (row_index + 1) & ODITHER_MASK; cquantize.row_index = row_index; } }
// Fast path for out_color_components==3, no dithering static void color_quantize3(jpeg_decompress cinfo, byte[][] input_buf, uint input_row, byte[][] output_buf, uint output_row, int num_rows) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; byte[] colorindex0 = cquantize.colorindex[0]; byte[] colorindex1 = cquantize.colorindex[1]; byte[] colorindex2 = cquantize.colorindex[2]; uint width = cinfo.output_width; for (int row = 0; row < num_rows; row++) { byte[] ptrin = input_buf[input_row + row]; byte[] ptrout = output_buf[output_row + row]; uint iind = 0, oind = 0; for (uint col = width; col > 0; col--) { int pixcode = colorindex0[ptrin[iind++]]; pixcode += colorindex1[ptrin[iind++]]; pixcode += colorindex2[ptrin[iind++]]; ptrout[oind++] = (byte)pixcode; } } }
// Create the ordered-dither tables. // Components having the same number of representative colors may // share a dither table. static void create_odither_tables(jpeg_decompress cinfo) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; for (int i = 0; i < cinfo.out_color_components; i++) { int nci = cquantize.Ncolors[i]; // # of distinct values for this color int[,] odither = null; // search for matching prior component for (int j = 0; j < i; j++) { if (nci == cquantize.Ncolors[j]) { odither = cquantize.odither[j]; break; } } if (odither == null) { odither = make_odither_array(cinfo, nci); // need a new table? } cquantize.odither[i] = odither; } }
// Map some rows of pixels to the output colormapped representation. // General case, no dithering static void color_quantize(jpeg_decompress cinfo, byte[][] input_buf, uint input_row, byte[][] output_buf, uint output_row, int num_rows) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; byte[][] colorindex = cquantize.colorindex; uint width = cinfo.output_width; int nc = cinfo.out_color_components; for (int row = 0; row < num_rows; row++) { byte[] ptrin = input_buf[input_row + row]; byte[] ptrout = output_buf[output_row + row]; uint iind = 0, oind = 0; for (uint col = width; col > 0; col--) { int pixcode = 0; for (int ci = 0; ci < nc; ci++) { pixcode += colorindex[ci][ptrin[iind++]]; } ptrout[oind++] = (byte)pixcode; } } }
// Initialize for one-pass color quantization. static void start_pass_1_quant(jpeg_decompress cinfo, bool is_pre_scan) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; // Install my colormap. cinfo.colormap = cquantize.sv_colormap; cinfo.actual_number_of_colors = cquantize.sv_actual; // Initialize for desired dithering mode. switch (cinfo.dither_mode) { case J_DITHER_MODE.JDITHER_NONE: if (cinfo.out_color_components == 3) { cquantize.color_quantize = color_quantize3; } else { cquantize.color_quantize = color_quantize; } break; case J_DITHER_MODE.JDITHER_ORDERED: if (cinfo.out_color_components == 3) { cquantize.color_quantize = quantize3_ord_dither; } else { cquantize.color_quantize = quantize_ord_dither; } cquantize.row_index = 0; // initialize state for ordered dither // Create ordered-dither tables if we didn't already. if (cquantize.odither[0] == null) { create_odither_tables(cinfo); } break; case J_DITHER_MODE.JDITHER_FS: cquantize.color_quantize = quantize_fs_dither; cquantize.on_odd_row = false; // initialize state for F-S dither // Allocate Floyd-Steinberg workspace if didn't already. if (cquantize.fserrors[0] == null) { alloc_fs_workspace(cinfo); } // Initialize the propagated errors to zero. uint arraysize = cinfo.output_width + 2; for (int i = 0; i < cinfo.out_color_components; i++) { for (int j = 0; j < arraysize; j++) { cquantize.fserrors[i][j] = 0; } } break; default: ERREXIT(cinfo, J_MESSAGE_CODE.JERR_NOT_COMPILED); break; } }
// General case, with Floyd-Steinberg dithering static void quantize_fs_dither(jpeg_decompress cinfo, byte[][] input_buf, uint input_row, byte[][] output_buf, uint output_row, int num_rows) { my_cquantizer1 cquantize = (my_cquantizer1)cinfo.cquantize; int[] errorptr; // => fserrors[] at column before current int errorptr_ind; int pixcode; int nc = cinfo.out_color_components; int dir; // 1 for left-to-right, -1 for right-to-left int dirnc; // dir * nc uint width = cinfo.output_width; for (int row = 0; row < num_rows; row++) { // Initialize output values to 0 so can process components separately for (int i = 0; i < width; i++) { output_buf[output_row + row][i] = 0; } for (int ci = 0; ci < nc; ci++) { byte[] input_ptr = input_buf[input_row + row]; int iind = ci; byte[] output_ptr = output_buf[output_row + row]; int oind = 0; if (cquantize.on_odd_row) { // work right to left in this row iind += (int)(width - 1) * nc; // so point to rightmost pixel oind += (int)width - 1; dir = -1; dirnc = -nc; errorptr = cquantize.fserrors[ci]; errorptr_ind = (int)(width + 1); // => entry after last column } else { // work left to right in this row dir = 1; dirnc = nc; errorptr = cquantize.fserrors[ci]; // => entry before first column errorptr_ind = 0; } byte[] colorindex_ci = cquantize.colorindex[ci]; byte[] colormap_ci = cquantize.sv_colormap[ci]; // Preset error values: no error propagated to first pixel from left int cur = 0; // and no error propagated to row below yet int belowerr = 0, bpreverr = 0; for (uint col = width; col > 0; col--) { // cur holds the error propagated from the previous pixel on the // current line. Add the error propagated from the previous line // to form the complete error correction term for this pixel, and // round the error term (which is expressed * 16) to an integer. // Right shift rounds towards minus infinity, so adding 8 is correct // for either sign of the error value. // Note: errorptr points to *previous* column's array entry. cur = (cur + errorptr[errorptr_ind + dir] + 8) >> 4; // Form pixel value + error, and range-limit to 0..MAXJSAMPLE. // The maximum error is +- MAXJSAMPLE; this sets the required size // of the range_limit array. cur += input_ptr[iind]; cur = (cur >= MAXJSAMPLE?MAXJSAMPLE:(cur < 0?0:cur)); // Select output value, accumulate into output code for this pixel pixcode = colorindex_ci[cur]; output_ptr[oind] += (byte)pixcode; // Compute actual representation error at this pixel // Note: we can do this even though we don't have the final // pixel code, because the colormap is orthogonal. cur -= colormap_ci[pixcode]; // Compute error fractions to be propagated to adjacent pixels. // Add these into the running sums, and simultaneously shift the // next-line error sums left by 1 column. int bnexterr = cur; int delta = cur * 2; cur += delta; // form error * 3 errorptr[errorptr_ind] = bpreverr + cur; cur += delta; // form error * 5 bpreverr = belowerr + cur; belowerr = bnexterr; cur += delta; // form error * 7 // At this point cur contains the 7/16 error value to be propagated // to the next pixel on the current line, and all the errors for the // next line have been shifted over. We are therefore ready to move on. iind += dirnc; // advance input ptr to next column oind += dir; // advance output ptr to next column errorptr_ind += dir; // advance errorptr to current column } // Post-loop cleanup: we must unload the final error value into the // final fserrors[] entry. Note we need not unload belowerr because // it is for the dummy column before or after the actual array. errorptr[errorptr_ind] = bpreverr; // unload prev err into array } cquantize.on_odd_row = (cquantize.on_odd_row?false:true); } }
// Module initialization routine for 1-pass color quantization. public static void jinit_1pass_quantizer(jpeg_decompress cinfo) { my_cquantizer1 cquantize=null; try { cquantize=new my_cquantizer1(); } catch { ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_OUT_OF_MEMORY, 4); } cinfo.cquantize=cquantize; cquantize.start_pass=start_pass_1_quant; cquantize.finish_pass=finish_pass_1_quant; cquantize.new_color_map=new_color_map_1_quant; cquantize.fserrors[0]=null; // Flag FS workspace not allocated cquantize.odither[0]=null; // Also flag odither arrays not allocated // Make sure my internal arrays won't overflow if(cinfo.out_color_components>MAX_Q_COMPS) ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_QUANT_COMPONENTS, MAX_Q_COMPS); // Make sure colormap indexes can be represented by bytes if(cinfo.desired_number_of_colors>(MAXJSAMPLE+1)) ERREXIT1(cinfo, J_MESSAGE_CODE.JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1); // Create the colormap and color index table. create_colormap(cinfo); create_colorindex(cinfo); // Allocate Floyd-Steinberg workspace now if requested. // We do this now since it is storage and may effect the memory // manager's space calculations. If the user changes to FS dither // mode in a later pass, we will allocate the space then, and will // possibly overrun the max_memory_to_use setting. if(cinfo.dither_mode==J_DITHER_MODE.JDITHER_FS) alloc_fs_workspace(cinfo); }