/* Copyright (C) 1998, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: sdcparam.c,v 1.3.2.1.2.1 2003/01/17 00:49:05 giles Exp $ */
/* DCT filter parameter setting and reading */
#include "memory_.h"
#include "jpeglib_.h"
#include "gserror.h"
#include "gserrors.h"
#include "gstypes.h"
#include "gsmemory.h"
#include "gsparam.h"
#include "strimpl.h"		/* sdct.h requires this */
#include "sdct.h"
#include "sdcparam.h"
#include "sjpeg.h"

/* Define the DCT parameters. */
#define dctp(key, type, stype, memb) { key, type, offset_of(stype, memb) }
private const gs_param_item_t s_DCT_param_items[] =
{
dctp("ColorTransform", gs_param_type_int, stream_DCT_state, ColorTransform),
    dctp("QFactor", gs_param_type_float, stream_DCT_state, QFactor),
    gs_param_item_end
};
private const gs_param_item_t jsd_param_items[] =
{
    dctp("Picky", gs_param_type_int, jpeg_stream_data, Picky),
    dctp("Relax", gs_param_type_int, jpeg_stream_data, Relax),
    gs_param_item_end
};

#undef dctp

/*
 * Adobe specifies the values to be supplied in zigzag order.
 * For IJG versions newer than v6, we need to convert this order
 * to natural array order.  Older IJG versions want zigzag order.
 */
#if JPEG_LIB_VERSION >= 61
	/* natural array position of n'th element of JPEG zigzag order */
static const byte natural_order[DCTSIZE2] =
{
    0, 1, 8, 16, 9, 2, 3, 10,
    17, 24, 32, 25, 18, 11, 4, 5,
    12, 19, 26, 33, 40, 48, 41, 34,
    27, 20, 13, 6, 7, 14, 21, 28,
    35, 42, 49, 56, 57, 50, 43, 36,
    29, 22, 15, 23, 30, 37, 44, 51,
    58, 59, 52, 45, 38, 31, 39, 46,
    53, 60, 61, 54, 47, 55, 62, 63
};

#define jpeg_order(x)  natural_order[x]
	/* invert natural_order for getting parameters */
static const byte inverse_natural_order[DCTSIZE2] =
{
    0, 1, 5, 6, 14, 15, 27, 28,
    2, 4, 7, 13, 16, 26, 29, 42,
    3, 8, 12, 17, 25, 30, 41, 43,
    9, 11, 18, 24, 31, 40, 44, 53,
    10, 19, 23, 32, 39, 45, 52, 54,
    20, 22, 33, 38, 46, 51, 55, 60,
    21, 34, 37, 47, 50, 56, 59, 61,
    35, 36, 48, 49, 57, 58, 62, 63
};

#define jpeg_inverse_order(x)  inverse_natural_order[x]
#else
#define jpeg_order(x)  (x)
#define jpeg_inverse_order(x) (x)
#endif

/* ================ Get parameters ================ */

private int
quant_param_string(gs_param_string * pstr, int count, const UINT16 * pvals,
		   floatp QFactor, gs_memory_t * mem)
{
    byte *data;
    int code = 0;
    int i;

    data = gs_alloc_string(mem, count, "quant_param_string");
    if (data == 0)
	return_error(gs_error_VMerror);
    for (i = 0; i < count; ++i) {
	floatp val = pvals[jpeg_inverse_order(i)] / QFactor;

	data[i] =
	    (val < 1 ? (code = 1) : val > 255 ? (code = 255) : (byte) val);
    }
    pstr->data = data;
    pstr->size = count;
    pstr->persistent = true;
    return code & 1;
}

private int
quant_param_array(gs_param_float_array * pfa, int count, const UINT16 * pvals,
		  floatp QFactor, gs_memory_t * mem)
{
    float *data;
    int i;

    data = (float *)gs_alloc_byte_array(mem, count, sizeof(float),
					"quant_param_array");

    if (data == 0)
	return_error(gs_error_VMerror);
    for (i = 0; i < count; ++i)
	data[i] = pvals[jpeg_inverse_order(i)] / QFactor;
    pfa->data = data;
    pfa->size = count;
    pfa->persistent = true;
    return 0;
}

int
s_DCT_get_quantization_tables(gs_param_list * plist,
	   const stream_DCT_state * pdct, const stream_DCT_state * defaults,
			      bool is_encode)
{
    gs_memory_t *mem = pdct->memory;
    jpeg_component_info d_comp_info[4];
    int num_in_tables;
    const jpeg_component_info *comp_info;
    const jpeg_component_info *default_comp_info;
    JQUANT_TBL **table_ptrs;
    JQUANT_TBL **default_table_ptrs;
    gs_param_array quant_tables;
    floatp QFactor = pdct->QFactor;
    int i;
    int code;

    if (is_encode) {
	num_in_tables = pdct->data.compress->cinfo.num_components;
	comp_info = pdct->data.compress->cinfo.comp_info;
	table_ptrs = pdct->data.compress->cinfo.quant_tbl_ptrs;
	if (defaults) {
	    default_comp_info = defaults->data.compress->cinfo.comp_info;
	    default_table_ptrs = defaults->data.compress->cinfo.quant_tbl_ptrs;
	}
    } else {
	/**************** quant_tables.size NOT INITIALIZED ****************/
	num_in_tables = quant_tables.size;
	for (i = 0; i < num_in_tables; ++i)
	    d_comp_info[i].quant_tbl_no = i;
	comp_info = d_comp_info;
	table_ptrs = pdct->data.decompress->dinfo.quant_tbl_ptrs;
	if (defaults) {
	    default_comp_info = d_comp_info;
	    default_table_ptrs =
		defaults->data.decompress->dinfo.quant_tbl_ptrs;
	}
    }

    /* Check whether all tables match defaults. */
    if (defaults) {
	bool match = true;

	for (i = 0; i < num_in_tables; ++i) {
	    JQUANT_TBL *tbl = table_ptrs[comp_info[i].quant_tbl_no];
	    JQUANT_TBL *default_tbl =
	    (default_comp_info == 0 || default_table_ptrs == 0 ? 0 :
	     default_table_ptrs[default_comp_info[i].quant_tbl_no]);

	    if (tbl == default_tbl)
		continue;
	    if (tbl == 0 || default_tbl == 0 ||
		memcmp(tbl->quantval, default_tbl->quantval,
		       DCTSIZE2 * sizeof(UINT16))
		) {
		match = false;
		break;
	    }
	}
	if (match)
	    return 0;
    }
    quant_tables.size = num_in_tables;
    code = param_begin_write_collection(plist, "QuantTables",
					&quant_tables,
					gs_param_collection_array);
    if (code < 0)
	return code;
    for (i = 0; i < num_in_tables; ++i) {
	char key[3];
	gs_param_string str;
	gs_param_float_array fa;

	sprintf(key, "%d", i);
	if (QFactor == 1.0) {
	    code = quant_param_string(&str, DCTSIZE2,
			    table_ptrs[comp_info[i].quant_tbl_no]->quantval,
				      QFactor, mem);
	    switch (code) {
		case 0:
		    code = param_write_string(quant_tables.list, key, &str);
		    if (code < 0)
			return code;	/* should dealloc */
		    continue;
		default:
		    return code;	/* should dealloc */
		case 1:
		    break;
	    }
	    /* break const to free the string */
	    gs_free_string(mem, (byte *) str.data, str.size,
			   "quant_param_string");
	}
	code = quant_param_array(&fa, DCTSIZE2,
			    table_ptrs[comp_info[i].quant_tbl_no]->quantval,
				 QFactor, mem);
	if (code < 0)
	    return code;	/* should dealloc */
	code = param_write_float_array(quant_tables.list, key, &fa);
	if (code < 0)
	    return code;	/* should dealloc */
    }
    return param_end_write_dict(plist, "QuantTables", &quant_tables);
}

private int
pack_huff_table(gs_param_string * pstr, const JHUFF_TBL * table,
		gs_memory_t * mem)
{
    int total;
    int i;
    byte *data;

    for (i = 1, total = 0; i <= 16; ++i)
	total += table->bits[i];
    data = gs_alloc_string(mem, 16 + total, "pack_huff_table");
    if (data == 0)
	return_error(gs_error_VMerror);
    memcpy(data, table->bits + 1, 16);
    memcpy(data + 16, table->huffval, total);
    pstr->data = data;
    pstr->size = 16 + total;
    pstr->persistent = true;
    return 0;
}

int
s_DCT_get_huffman_tables(gs_param_list * plist,
	   const stream_DCT_state * pdct, const stream_DCT_state * defaults,
			 bool is_encode)
{
    gs_memory_t *mem = pdct->memory;
    gs_param_string *huff_data;
    gs_param_string_array hta;
    int num_in_tables;
    jpeg_component_info *comp_info;
    JHUFF_TBL **dc_table_ptrs;
    JHUFF_TBL **ac_table_ptrs;
    int i;
    int code = 0;

    if (is_encode) {
	dc_table_ptrs = pdct->data.compress->cinfo.dc_huff_tbl_ptrs;
	ac_table_ptrs = pdct->data.compress->cinfo.ac_huff_tbl_ptrs;
	num_in_tables = pdct->data.compress->cinfo.input_components * 2;
	comp_info = pdct->data.compress->cinfo.comp_info;
    } else {
	dc_table_ptrs = pdct->data.decompress->dinfo.dc_huff_tbl_ptrs;
	ac_table_ptrs = pdct->data.decompress->dinfo.ac_huff_tbl_ptrs;
	for (i = 2; i > 0; --i)
	    if (dc_table_ptrs[i - 1] || ac_table_ptrs[i - 1])
		break;
	num_in_tables = i * 2;
	comp_info = NULL;	/* do not set for decompress case */
    }
/****** byte_array IS WRONG ******/
    huff_data = (gs_param_string *)
	gs_alloc_byte_array(mem, num_in_tables, sizeof(gs_param_string),
			    "get huffman tables");
    if (huff_data == 0)
	return_error(gs_error_VMerror);
    for (i = 0; i < num_in_tables; i += 2) {
	if ((code = pack_huff_table(huff_data + i, ac_table_ptrs[i >> 1], mem)) < 0 ||
	    (code = pack_huff_table(huff_data + i + 1, dc_table_ptrs[i >> 1], mem))
	    )
	    break;
    }
    if (code < 0)
	return code;
    hta.data = huff_data;
    hta.size = num_in_tables;
    hta.persistent = true;
    return param_write_string_array(plist, "HuffTables", &hta);
}

int
s_DCT_get_params(gs_param_list * plist, const stream_DCT_state * ss,
		 const stream_DCT_state * defaults)
{
    int code =
    gs_param_write_items(plist, ss, defaults, s_DCT_param_items);

    if (code >= 0)
	code = gs_param_write_items(plist, ss->data.common,
				    (defaults ? defaults->data.common :
				     NULL),
				    jsd_param_items);
    return code;
}

/* ================ Put parameters ================ */

stream_state_proc_put_params(s_DCT_put_params, stream_DCT_state);	/* check */

/* ---------------- Utilities ---------------- */

/*
 * Get N byte-size values from an array or a string.
 * Used for HuffTables, HSamples, VSamples.
 */
int
s_DCT_byte_params(gs_param_list * plist, gs_param_name key, int start,
		  int count, UINT8 * pvals)
{
    int i;
    gs_param_string bytes;
    gs_param_float_array floats;
    int code = param_read_string(plist, key, &bytes);

    switch (code) {
	case 0:
	    if (bytes.size < start + count) {
		code = gs_note_error(gs_error_rangecheck);
		break;
	    }
	    for (i = 0; i < count; ++i)
		pvals[i] = (UINT8) bytes.data[start + i];
	    return 0;
	default:		/* might be a float array */
	    code = param_read_float_array(plist, key, &floats);
	    if (!code) {
		if (floats.size < start + count) {
		    code = gs_note_error(gs_error_rangecheck);
		    break;
		}
		for (i = 0; i < count; ++i) {
		    float v = floats.data[start + i];

		    if (v < 0 || v > 255) {
			code = gs_note_error(gs_error_rangecheck);
			break;
		    }
		    pvals[i] = (UINT8) (v + 0.5);
		}
	    }
    }
    if (code < 0)
	param_signal_error(plist, key, code);
    return code;
}

/* Get N quantization values from an array or a string. */
private int
quant_params(gs_param_list * plist, gs_param_name key, int count,
	     UINT16 * pvals, floatp QFactor)
{
    int i;
    gs_param_string bytes;
    gs_param_float_array floats;
    int code = param_read_string(plist, key, &bytes);

    switch (code) {
	case 0:
	    if (bytes.size != count) {
		code = gs_note_error(gs_error_rangecheck);
		break;
	    }
	    for (i = 0; i < count; ++i) {
		double v = bytes.data[i] * QFactor;

		pvals[jpeg_order(i)] =
		    (UINT16) (v < 1 ? 1 : v > 255 ? 255 : v + 0.5);
	    }
	    return 0;
	default:		/* might be a float array */
	    code = param_read_float_array(plist, key, &floats);
	    if (!code) {
		if (floats.size != count) {
		    code = gs_note_error(gs_error_rangecheck);
		    break;
		}
		for (i = 0; i < count; ++i) {
		    double v = floats.data[i] * QFactor;

		    pvals[jpeg_order(i)] =
			(UINT16) (v < 1 ? 1 : v > 255 ? 255 : v + 0.5);
		}
	    }
    }
    if (code < 0)
	param_signal_error(plist, key, code);
    return code;
#undef jpeg_order
}

/* ---------------- Main procedures ---------------- */

/* Put common scalars. */
int
s_DCT_put_params(gs_param_list * plist, stream_DCT_state * pdct)
{
    int code =
    gs_param_read_items(plist, pdct, s_DCT_param_items);

    if (code < 0)
	return code;
    code = gs_param_read_items(plist, pdct->data.common, jsd_param_items);
    if (code < 0)
	return code;
    if (pdct->data.common->Picky < 0 || pdct->data.common->Picky > 1 ||
	pdct->data.common->Relax < 0 || pdct->data.common->Relax > 1 ||
	pdct->ColorTransform < -1 || pdct->ColorTransform > 2 ||
	pdct->QFactor < 0.0 || pdct->QFactor > 1000000.0
	)
	return_error(gs_error_rangecheck);
    return 0;
}

/* Put quantization tables. */
int
s_DCT_put_quantization_tables(gs_param_list * plist, stream_DCT_state * pdct,
			      bool is_encode)
{
    int code;
    int i, j;
    gs_param_array quant_tables;	/* array of strings/arrays */
    int num_in_tables;
    int num_out_tables;
    jpeg_component_info *comp_info;
    JQUANT_TBL **table_ptrs;
    JQUANT_TBL *this_table;

    switch ((code = param_begin_read_dict(plist, "QuantTables",
					  &quant_tables, true))
	) {
	case 1:
	    return 1;
	default:
	    return param_signal_error(plist, "QuantTables", code);
	case 0:
	    ;
    }
    if (is_encode) {
	num_in_tables = pdct->data.compress->cinfo.num_components;
	if (quant_tables.size < num_in_tables)
	    return_error(gs_error_rangecheck);
	comp_info = pdct->data.compress->cinfo.comp_info;
	table_ptrs = pdct->data.compress->cinfo.quant_tbl_ptrs;
    } else {
	num_in_tables = quant_tables.size;
	comp_info = NULL;	/* do not set for decompress case */
	table_ptrs = pdct->data.decompress->dinfo.quant_tbl_ptrs;
    }
    num_out_tables = 0;
    for (i = 0; i < num_in_tables; ++i) {
	char istr[5];		/* i converted to string key */
	UINT16 values[DCTSIZE2];

	sprintf(istr, "%d", i);
	code = quant_params(quant_tables.list, istr, DCTSIZE2, values,
			    pdct->QFactor);
	if (code < 0)
	    return code;
	/* Check for duplicate tables. */
	for (j = 0; j < num_out_tables; j++) {
	    if (!memcmp(table_ptrs[j]->quantval, values, sizeof(values)))
		break;
	}
	if (comp_info != NULL)
	    comp_info[i].quant_tbl_no = j;
	if (j < num_out_tables)	/* found a duplicate */
	    continue;
	if (++num_out_tables > NUM_QUANT_TBLS)
	    return_error(gs_error_rangecheck);
	this_table = table_ptrs[j];
	if (this_table == NULL) {
	    this_table = gs_jpeg_alloc_quant_table(pdct);
	    if (this_table == NULL)
		return_error(gs_error_VMerror);
	    table_ptrs[j] = this_table;
	}
	memcpy(this_table->quantval, values, sizeof(values));
    }
    return 0;
}

/* Put Huffman tables. */
private int
find_huff_values(JHUFF_TBL ** table_ptrs, int num_tables,
	       const UINT8 counts[16], const UINT8 * values, int codes_size)
{
    int j;

    for (j = 0; j < num_tables; ++j)
	if (!memcmp(table_ptrs[j]->bits, counts, sizeof(counts)) &&
	    !memcmp(table_ptrs[j]->huffval, values,
		    codes_size * sizeof(values[0])))
	    break;
    return j;
}
int
s_DCT_put_huffman_tables(gs_param_list * plist, stream_DCT_state * pdct,
			 bool is_encode)
{
    int code;
    int i, j;
    gs_param_array huff_tables;
    int num_in_tables;
    int ndc, nac;
    int codes_size;
    jpeg_component_info *comp_info;
    JHUFF_TBL **dc_table_ptrs;
    JHUFF_TBL **ac_table_ptrs;
    JHUFF_TBL **this_table_ptr;
    JHUFF_TBL *this_table;
    int max_tables = 2;		/* baseline limit */

    switch ((code = param_begin_read_dict(plist, "HuffTables",
					  &huff_tables, true))
	) {
	case 1:
	    return 0;
	default:
	    return param_signal_error(plist, "HuffTables", code);
	case 0:
	    ;
    }
    if (is_encode) {
	num_in_tables = pdct->data.compress->cinfo.input_components * 2;
	if (huff_tables.size < num_in_tables)
	    return_error(gs_error_rangecheck);
	comp_info = pdct->data.compress->cinfo.comp_info;
	dc_table_ptrs = pdct->data.compress->cinfo.dc_huff_tbl_ptrs;
	ac_table_ptrs = pdct->data.compress->cinfo.ac_huff_tbl_ptrs;
	if (pdct->data.common->Relax)
	    max_tables = max(pdct->data.compress->cinfo.input_components, 2);
    } else {
	num_in_tables = huff_tables.size;
	comp_info = NULL;	/* do not set for decompress case */
	dc_table_ptrs = pdct->data.decompress->dinfo.dc_huff_tbl_ptrs;
	ac_table_ptrs = pdct->data.decompress->dinfo.ac_huff_tbl_ptrs;
	if (pdct->data.common->Relax)
	    max_tables = NUM_HUFF_TBLS;
    }
    ndc = nac = 0;
    for (i = 0; i < num_in_tables; ++i) {
	char istr[5];		/* i converted to string key */
	UINT8 counts[16], values[256];

	/* Collect the Huffman parameters. */
	sprintf(istr, "%d", i);
	code = s_DCT_byte_params(huff_tables.list, istr, 0, 16, counts);
	if (code < 0)
	    return code;
	for (codes_size = 0, j = 0; j < 16; j++)
	    codes_size += counts[j];
	if (codes_size > 256 /*|| r_size(pa) != codes_size+16 */ )
	    return_error(gs_error_rangecheck);
	code = s_DCT_byte_params(huff_tables.list, istr, 16, codes_size,
				 values);
	if (code < 0)
	    return code;
	if (i & 1) {
	    j = find_huff_values(ac_table_ptrs, nac, counts, values,
				 codes_size);
	    if (comp_info != NULL)
		comp_info[i >> 1].ac_tbl_no = j;
	    if (j < nac)
		continue;
	    if (++nac > NUM_HUFF_TBLS)
		return_error(gs_error_rangecheck);
	    this_table_ptr = ac_table_ptrs + j;
	} else {
	    j = find_huff_values(dc_table_ptrs, ndc, counts, values,
				 codes_size);
	    if (comp_info != NULL)
		comp_info[i >> 1].dc_tbl_no = j;
	    if (j < ndc)
		continue;
	    if (++ndc > NUM_HUFF_TBLS)
		return_error(gs_error_rangecheck);
	    this_table_ptr = dc_table_ptrs + j;
	}
	this_table = *this_table_ptr;
	if (this_table == NULL) {
	    this_table = gs_jpeg_alloc_huff_table(pdct);
	    if (this_table == NULL)
		return_error(gs_error_VMerror);
	    *this_table_ptr = this_table;
	}
	memcpy(this_table->bits, counts, sizeof(counts));
	memcpy(this_table->huffval, values, codes_size * sizeof(values[0]));
    }
    if (nac > max_tables || ndc > max_tables)
	return_error(gs_error_rangecheck);
    return 0;
}
