/* quantise an image with libimagequant
 *
 * 20/6/18 
 * 	  - from vipspng.c
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
#define VIPS_DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <vips/vips.h>

#ifdef HAVE_IMAGEQUANT

#include "pforeign.h"

#include <libimagequant.h>

/* Track during a quantisation.
 */
typedef struct _Quantise {
	VipsImage *in;
	VipsImage **index_out;
	VipsImage **palette_out;
	int colours;
	int Q;
	double dither;
	int effort;

	liq_attr *attr;
	liq_image *input_image;
	liq_result *quantisation_result;
	VipsImage *t[5];
} Quantise;

static void
vips__quantise_free( Quantise *quantise )
{
	int i;

	VIPS_FREEF( liq_result_destroy, quantise->quantisation_result );
	VIPS_FREEF( liq_image_destroy, quantise->input_image );
	VIPS_FREEF( liq_attr_destroy, quantise->attr );

	for( i = 0; i < VIPS_NUMBER( quantise->t ); i++ )
		VIPS_UNREF( quantise->t[i] ); 

	VIPS_FREE( quantise );
}

static Quantise *
vips__quantise_new( VipsImage *in, 
	VipsImage **index_out, VipsImage **palette_out,
        int colours, int Q, double dither, int effort )
{
	Quantise *quantise;
	int i;

	quantise = VIPS_NEW( NULL, Quantise );
	quantise->in = in;
	quantise->index_out = index_out;
	quantise->palette_out = palette_out;
	quantise->colours = colours;
	quantise->Q = Q;
	quantise->dither = dither;
	quantise->effort = effort;
	for( i = 0; i < VIPS_NUMBER( quantise->t ); i++ )
		quantise->t[i] = NULL; 

	return( quantise ); 
}

int
vips__quantise_image( VipsImage *in, 
	VipsImage **index_out, VipsImage **palette_out,
	int colours, int Q, double dither, int effort,
	gboolean threshold_alpha )
{
	Quantise *quantise;
	VipsImage *index;
	VipsImage *palette;
	const liq_palette *lp;
	gint64 i;
	VipsPel * restrict p;
	gboolean added_alpha;

	quantise = vips__quantise_new( in, index_out, palette_out, 
		colours, Q, dither, effort );

	/* Ensure input is sRGB. 
	 */
	if( in->Type != VIPS_INTERPRETATION_sRGB ) {
		if( vips_colourspace( in, &quantise->t[0], 
			VIPS_INTERPRETATION_sRGB, NULL ) ) {
			vips__quantise_free( quantise ); 
			return( -1 );
		}
		in = quantise->t[0];
	}

	/* Add alpha channel if missing. 
	 */
	added_alpha = FALSE;
	if( !vips_image_hasalpha( in ) ) {
		if( vips_bandjoin_const1( in, &quantise->t[1], 255, NULL ) ) {
			vips__quantise_free( quantise ); 
			return( -1 );
		}
		added_alpha = TRUE;
		in = quantise->t[1];
	}

	if( !(quantise->t[2] = vips_image_copy_memory( in )) ) {
		vips__quantise_free( quantise ); 
		return( -1 );
	}
	in = quantise->t[2];

	/* Threshold alpha channel.
	 */
	if( threshold_alpha && 
		!added_alpha ) {
		const guint64 n_pels = VIPS_IMAGE_N_PELS( in );

		p = VIPS_IMAGE_ADDR( in, 0, 0 );
		for( i = 0; i < n_pels; i++ ) {
			p[3] = p[3] > 128 ? 255 : 0;
			p += 4;
		}
	}

	quantise->attr = liq_attr_create();
	liq_set_max_colors( quantise->attr, colours );
	liq_set_quality( quantise->attr, 0, Q );
	liq_set_speed( quantise->attr, 11 - effort );

	quantise->input_image = liq_image_create_rgba( quantise->attr,
		VIPS_IMAGE_ADDR( in, 0, 0 ), in->Xsize, in->Ysize, 0 );

	if( liq_image_quantize( quantise->input_image, quantise->attr, 
		&quantise->quantisation_result ) ) {
		vips_error( "quantise", "%s", _( "quantisation failed" ) );
		vips__quantise_free( quantise ); 
		return( -1 );
	}

	liq_set_dithering_level( quantise->quantisation_result, dither );

	index = quantise->t[3] = vips_image_new_memory();
	vips_image_init_fields( index, 
		in->Xsize, in->Ysize, 1, VIPS_FORMAT_UCHAR,
		VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 );

	if( vips_image_write_prepare( index ) ) {
		vips__quantise_free( quantise ); 
		return( -1 );
	}

	if( liq_write_remapped_image( quantise->quantisation_result, 
		quantise->input_image,
		VIPS_IMAGE_ADDR( index, 0, 0 ), VIPS_IMAGE_N_PELS( index ) ) ) {
		vips_error( "quantise", "%s", _( "quantisation failed" ) );
		vips__quantise_free( quantise ); 
		return( -1 );
	}

	lp = liq_get_palette( quantise->quantisation_result );

	palette = quantise->t[4] = vips_image_new_memory();
	vips_image_init_fields( palette, lp->count, 1, 4,
		VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB,
		1.0, 1.0 );

	if( vips_image_write_prepare( palette ) ) {
		vips__quantise_free( quantise ); 
		return( -1 );
	}

	p = VIPS_IMAGE_ADDR( palette, 0, 0 );
	for( i = 0; i < lp->count; i++ ) {
		p[0] = lp->entries[i].r;
		p[1] = lp->entries[i].g;
		p[2] = lp->entries[i].b;
		p[3] = lp->entries[i].a;

		p += 4;
	}

	*index_out = index;
	g_object_ref( index );
	*palette_out = palette;
	g_object_ref( palette );

	vips__quantise_free( quantise ); 

	return( 0 );
}

#else /*!HAVE_IMAGEQUANT*/

int
vips__quantise_image( VipsImage *in, 
	VipsImage **index_out, VipsImage **palette_out,
	int colours, int Q, double dither, int effort,
	gboolean threshold_alpha )
{
	vips_error( "vips__quantise_image", 
		"%s", _( "libvips not built with quantisation support" ) ); 

	return( -1 );
}

#endif /*HAVE_IMAGEQUANT*/

