# Lets add some some modern colour handling to surgeweb :-) 
# Colour spaces supported: rgb, hsl, hsv


function lerp(v1,t,v2)
{
	return (1-t)*v1 + (t)*v2;
}
function slerp01(v1,t,v2)
{
	var d = v2-v1;
	if (d>0.5) d-=1;
	var v = v1+d*t;
	if(v<0) v+=1;
	if(v>1) v-=1;
	return v;
}
function clamp(min, val, max)
{
	if (val < min) val = min;
	if (val > max) val = max;
	return val;
}
function clamp01(val)
{
	return clamp(0, val, 1);
}
function limit01(val)
{
	return clamp01(val);
}
function limit_below(limit,val)
{
	if (val > limit) val = limit;
	return val;
}
function limit_above(limit,val)
{
	if (val < limit) val = limit;
	return val;
}
function clamp_below(limit,val)
{
	return limit_below(limit,val)
}
function clamp_above(limit,val)
{
	return limit_above(limit,val)
}

function normalise_in_range(min, t, max)	// Clamp01MapToRange
{
	var result = 0;
	var range = max - min;
	result = clamp01((t - min) / range);
	return result;
}

function push_handler(el, evt, fn)
{
	var evt_stack = evt+'_stack';
	if(el[evt_stack]==undefined) el[evt_stack]=[];
	el[evt_stack].push(el[evt]);
	el[evt]=fn;
}
function pop_handler(el, evt)
{
	var evt_stack = evt+'_stack';
	el[evt]=el[evt_stack].pop();;
}

var rgb_r=0, rgb_g=1, rgb_b=2, rgb_a=3;
var hsl_h=0, hsl_s=1, hsl_l=2, hsl_a=3, hsl_b=2;
var hsv_h=0, hsv_s=1, hsv_v=2, hsc_a=3;

function minmax(min,val,max)
{
	if(val<min) val=min;
	if(val>max) val=max;
	return val;
}
function txt2col(txt)
{
	var col=[];
	col[0]=parseInt(txt.substring(1+0,1+2),16);
	col[1]=parseInt(txt.substring(1+2,1+4),16);
	col[2]=parseInt(txt.substring(1+4,1+6),16);
	col[3]=1;
	return col;
}
function multi_txt2col(txt)
{
	var col = [0,0,0,0];
	if(txt.indexOf('#')==0){
		col = txt2col(txt);
	}else{
		var parts = txt.split(/[, ]/);
		if(parts.length==3){
			col[0] = minmax(0,parseInt(parts[0]),255);
			col[1] = minmax(0,parseInt(parts[1]),255);
			col[2] = minmax(0,parseInt(parts[2]),255);
		}
	}
	return col;
}
function col2txt(col)
{
	var txt = '#' + fmt_hex(Math.floor(col[0])) + fmt_hex(Math.floor(col[1])) + fmt_hex(Math.floor(col[2]));
	return txt;
}
function col2txt_int(col)
{
	var txt = '' + Math.floor(col[0]) + ' ' + Math.floor(col[1]) + ' ' + Math.floor(col[2]);
	return txt;
}
function col_greyscale(col)
{
	col[0]=col[1]=col[2]=(col[0]+col[1]+col[2])/3;
	return col;
}
function hsl_delta_(col_in, dh, ds, dl)
{
	var col=[ limit01(col_in[hsl_h]+dh), limit01(col_in[hsl_s]+ds), limit01(col_in[hsl_b]+dl), col_in[hsl_a]];
	return col;
}
function hsl_modulate(col_in, dh, ds, dl)
{
	var h = dh<0 ? lerp(col_in[hsl_h], -dh, 0) : lerp(col_in[hsl_h], dh, 1);
	var s = ds<0 ? lerp(col_in[hsl_s], -ds, 0) : lerp(col_in[hsl_s], ds, 1);
	var l = dl<0 ? lerp(col_in[hsl_b], -dl, 0) : lerp(col_in[hsl_b], dl, 1);

	return hsl_init(h, s, l, col_in[hsl_a]);
}
function hsl_max_l(col,max_l)
{
	if(col[hsl_l]>max_l) col[hsl_l]=max_l;
	return col;
}
function hsl_min_l(col,min_l)
{
	if(col[hsl_l]<min_l) col[hsl_l]=min_l;
	return col;
}
function hsl_slerp(c1, t, c2)
{
	var col = hsl_init( slerp01(c1[hsl_h],t,c2[hsl_h]), lerp(c1[hsl_s],t,c2[hsl_s]), lerp(c1[hsl_l],t,c2[hsl_l]));
	return col;
}
function hsl_lerp(c1, t, c2)
{
	var rgb1=hsl2rgb(c1);
	var rgb2=hsl2rgb(c2);
	var rgb = rgb_init( lerp(rgb1[rgb_r],t,rgb2[rgb_r]), lerp(rgb1[rgb_g],t,rgb2[rgb_g]), lerp(rgb1[rgb_b],t,rgb2[rgb_b]));
	return rgb2hsl(rgb);
}
function hsl_init(h,s,l,a)
{
	return [ clamp01(h), clamp01(s), clamp01(l), (a==U ? 1 : a)];
}
function rgb_init(r,g,b,a)
{
	return [ clamp(0,r,255), clamp(0,g,255), clamp(0,b,255), (a==U ? 1 : a)];
}
function rgb01_init(r,g,b,a)
{
	return [ clamp01(r), clamp01(g), clamp01(b), (a==U ? 1 : a)];
}

function rgb2hsl_01(col)
{
	var r=col[0], g=col[1], b=col[2], a=col[3];
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;
    if(max == min){
        h = s = 0;
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l, a];
}
function rgb2hsl(col)
{
	var r=col[0], g=col[1], b=col[2], a=col[3];
    r /= 255, g /= 255, b /= 255;
    return rgb2hsl_01([r,g,b,a]);
}
function hsl2rgb_01(col)
{
	var h=col[0], s=col[1], l=col[2], a=col[3];
    var r, g, b;
    if(s == 0){
        r = g = b = l;
    }else{
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }
    return [r, g, b, a];
}
function hsl2rgb(col)
{
	col = hsl2rgb_01(col);
    return [Math.round(col[rgb_r] * 255), Math.round(col[rgb_g] * 255), Math.round(col[rgb_b] * 255), col[rgb_a]];
}
function rgb2hsv_01(col) 
{
	var r=col[0], g=col[1], b=col[2], a=col[3];

	var max = Math.max(r, g, b), min = Math.min(r, g, b);
	var h, s, v = max;

	var d = max - min;
	s = max == 0 ? 0 : d / max;

	if (max == min) {
		h = 0; // achromatic
	} else {
		switch (max) {
			case r: h = (g - b) / d + (g < b ? 6 : 0); break;
			case g: h = (b - r) / d + 2; break;
			case b: h = (r - g) / d + 4; break;
		}
		h /= 6;
	}
	return [ h, s, v, a];
}
function rgb2hsv(col) 
{
	var r=col[0], g=col[1], b=col[2], a=col[3];
	r /= 255, g /= 255, b /= 255;
	return rgb2hsv_01([r,g,b,a]);
}
function hsv2rgb_01(col) 
{
	var h=col[0], s=col[1], v=col[2], a=col[3];
	var r, g, b;

	var i = Math.floor(h * 6);
	var f = h * 6 - i;
	var p = v * (1 - s);
	var q = v * (1 - f * s);
	var t = v * (1 - (1 - f) * s);

	switch (i % 6) {
		case 0: r = v, g = t, b = p; break;
		case 1: r = q, g = v, b = p; break;
		case 2: r = p, g = v, b = t; break;
		case 3: r = p, g = q, b = v; break;
		case 4: r = t, g = p, b = v; break;
		case 5: r = v, g = p, b = q; break;
	}

	return [ r, g, b, a];
}
function hsv2rgb(col) 
{
	col = hsv2rgb_01(col);
	return [ col[rgb_r] * 255, col[rgb_g] * 255, col[rgb_r] * 255, col[rgb_a]];
}
function hsl2std(col)
{
	return hsl_init(col[hsl_h],1,0.5);
}


function hsl_out(col)
{
	return col2txt(hsl2rgb(col));
}
function hsl_out_int(col)
{
	return col2txt_int(hsl2rgb(col));
}
function hsv2hsl(src_col)
{
	var col = rgb2hsl_01(hsv2rgb_01(src_col));
	col[hsl_h]=src_col[hsl_h];
	return col;
}

# Colour picker written from scratch my marijn :-)
# inspiration:
# 	Colour triangle: http://timbaumann.info/colortriangle/
# 	Colour square: https://seesparkbox.com/foundry/how_i_built_a_canvas_color_picker
function palette_create(id)
{
	var o = {};
	var el1_id = id+'_pane1';
	var el2_id = id+'_pane2';
	if(!el_exists(el1_id) || !el_exists(el2_id)) {
		return;
	}
	o.pane1 = palette_pane1(el1_id);
  	o.pane2 = palette_pane2(el2_id);
  	function palette_render()
  	{
  		o.pane1.render();
  		o.pane2.render();
  	}
	function begin_delta()
	{
	  o.colorTimer = setInterval(color_changed, 50);
	}
	function end_delta()
	{
	  clearInterval(o.colorTimer);
	  o.render();
	}
	function palette_destroy()
	{
		
	}
	var count=0;
	function color_changed()
	{
		var col=hsv2hsl([o.pane2.colorEventX, o.pane1.colorEventX, o.pane1.colorEventY,1]);		
		o.pane1.base_color = hsl2std(col);
		o.on_change(col);
		o.render();		
	}
	function set_color(col_hsl, bypass)
	{		
	  	o.pane1.base_color=hsl2std(col_hsl);
	  	if(bypass) return;
	  	
		o.pane2.colorEventX = clamp01(col_hsl[hsl_h]);
		var col_hsv=rgb2hsv_01(hsl2rgb_01(col_hsl));
		o.pane1.colorEventX = clamp01(col_hsv[hsv_s]);
		o.pane1.colorEventY = clamp01(col_hsv[hsv_v]);
	}


  	o.pane1.begin_delta = o.pane2.begin_delta = begin_delta;
  	o.pane1.end_delta = o.pane2.end_delta = end_delta;
  	o.pane1.color=[0,0,0,1];
	o.on_change=function(){};
	o.render = palette_render;
  	o.set_color = set_color;
  	o.destroy = palette_destroy;
    return o;
}

function palette_pane1(id)
{
	var o = {};
	o.el = dge(id);
	o.ctx = o.el.getContext('2d');
	o.colorEventX = o.colorEventY = 0.5;
	o.myoffset=get_location(o.el);
	push_handler(o.el, "onmousedown", palette_mousedown);
	push_handler(o.el, "onmouseup", palette_mouseup);

	function record_mouse(e){
	  o.colorEventX = clamp(0, (e.pageX - o.myoffset.left)/o.ctx.canvas.width, 1);
	  o.colorEventY = 1-clamp(0, (e.pageY - o.myoffset.top)/o.ctx.canvas.height, 1);
	}
	function palette_mousedown(e){
	  push_handler(document, "onmousemove", palette_mousemove );
	  record_mouse(e);
	  o.begin_delta();
	}
	function palette_mousemove(e){
	  if(e.buttons==0) return o.el.onmouseup(e);
	  record_mouse(e);
	  stop_event(e);
	}
	function palette_mouseup(e){
	  pop_handler(document, "onmousemove");
	  o.end_delta();
	}
	function palette_render(){
	  var ctx = o.ctx;
	  var gradient = ctx.createLinearGradient(0,0,ctx.canvas.width,0);
	  gradient.addColorStop(0,    "rgb(255,255,255)");
	  gradient.addColorStop(1,    hsl_out(o.base_color));
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	  gradient = ctx.createLinearGradient(0,0,0,155);
	  gradient.addColorStop(0.0, "rgba(0,0,0, 0)");
	  gradient.addColorStop(1,   "rgba(0,0,0, 1)");
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

	  var r=7;
	  var x = o.colorEventX;
	  var y = o.colorEventY;

	  ctx.beginPath();
	  ctx.arc(x*ctx.canvas.width, (1-y)*ctx.canvas.height, r, 0, 2 * Math.PI, false);
	  ctx.lineWidth = 1;
	  ctx.strokeStyle = 'white';
	  ctx.stroke();
	}
	o.render = palette_render;
	return o;
}

function palette_pane2(id)
{
	var o = {};
	o.el = dge(id);
	o.ctx = o.el.getContext('2d');
	o.colorEventX = o.colorEventY = 0.5;
	o.myoffset=get_location(o.el);
	push_handler(o.el, "onmousedown", palette_mousedown);
	push_handler(o.el, "onmouseup", palette_mouseup);

	function record_mouse(e){
	  o.colorEventX = clamp(0, (e.pageX - o.myoffset.left)/o.ctx.canvas.width, 1);
	}
	function palette_mousedown(e){
	  push_handler(document, "onmousemove", palette_mousemove );
	  record_mouse(e);
	  o.begin_delta();
	}
	function palette_mousemove(e){
	  if(e.buttons==0) return o.el.onmouseup(e);
	  record_mouse(e);
	  stop_event(e);
	}
	function palette_mouseup(e){
	  pop_handler(document, "onmousemove");
	  o.end_delta();
	}
	function palette_render(){
	  var ctx = o.ctx;
	  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	  var gradient = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
	  gradient.addColorStop(0,    "rgb(255,   0,   0)");
	  gradient.addColorStop(0.15, "rgb(255, 255,   0)");
	  gradient.addColorStop(0.33, "rgb(0,   255,   0)");
	  gradient.addColorStop(0.49, "rgb(0,   255, 255)");
	  gradient.addColorStop(0.67, "rgb(0,     0, 255)");
	  gradient.addColorStop(0.84, "rgb(255,   0, 255)");
	  gradient.addColorStop(1,    "rgb(255,   0,   0)");
	  
	  var inset = 0;
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, +inset, ctx.canvas.width, ctx.canvas.height-inset*2);

	  var r=7;
	  var x = o.colorEventX;

	  ctx.beginPath();
#	  ctx.rect(x*ctx.canvas.width-0.5*r,ctx.canvas.height/2-1.5*r,r,2*1.5*r);
	  ctx.rect(x*ctx.canvas.width-0.5*r,0,r,ctx.canvas.height);
	  ctx.lineWidth = 2;
	  ctx.strokeStyle = 'black';
	  ctx.stroke();
	}
	o.render = palette_render;
	return o;
}


function range_decompress_side(val)
{	
	if (val<=0.7){
		return lerp(0,normalise_in_range(0,val,0.7),0.5);
	}else if(val<=0.9){
		return lerp(0.5,normalise_in_range(0.7,val,0.9),0.7);
	}else{
		return lerp(0.7,normalise_in_range(0.9,val,1.0),1.0);
	}
}

function range_decompress(val)
{	
	val = clamp01(val);
	if(val>=0.5){
		val = lerp(0.5,range_decompress_side(normalise_in_range(0.5,val,1)),1);
	} else {
		val = lerp(0.5,range_decompress_side(normalise_in_range(0.5,val,0)),0);
	}
	return val;
}
function range_compress_side(val)
{	
	if (val<=0.5){
		return lerp(0,normalise_in_range(0,val,0.5),0.7);
	}else if(val<=0.7){
		return lerp(0.7,normalise_in_range(0.5,val,0.7),0.9);
	}else{
		return lerp(0.9,normalise_in_range(0.7,val,1.0),1.0);
	}
}

function range_compress(val)
{	
	val = clamp01(val);
	if(val>=0.5){
		val = lerp(0.5,range_compress_side(normalise_in_range(0.5,val,1)),1);
	} else {
		val = lerp(0.5,range_compress_side(normalise_in_range(0.5,val,0)),0);
	}
	return val;
}


function unipalette_create(id)
{
	var o = {};
	o.pane1 = unipalette_pane1(id+'_pane1');
  	o.pane2 = unipalette_pane2(id+'_pane2');
  	if(!o.pane1 ||!o.pane2) return;  	
  	o.pane1.other_pane = o.pane2;
  	function palette_render()
  	{
  		o.pane1.render();
  		o.pane2.render();
  	}
	function begin_delta()
	{
	  o.colorTimer = setInterval(color_changed, 50);
	}
	function end_delta()
	{
	  clearInterval(o.colorTimer);
	  o.render();
	}
	function palette_destroy()
	{
		
	}
	var count=0;
	function color_changed()
	{
		var col=[o.pane1.colorEventX, o.pane2.colorEventX, o.pane1.colorEventY,1];		
		o.pane1.base_color = hsl2std(col);
		o.on_change(col);
		o.render();		
	}
	function set_color(col_hsl, bypass)
	{
#		var el = dge('theme_panel');
#		el.childNodes[2].style.backgroundColor = hsl_out(col_hsl);
	
	  	o.pane1.base_color=hsl2std(col_hsl);
	  	o.pane2.base_color=hsl2std(col_hsl);
	  	if(bypass) return;
	  	
		o.pane2.colorEventX = clamp01(col_hsl[hsl_s]);
		o.pane1.colorEventX = clamp01(col_hsl[hsl_h]);
		var col_hsv=rgb2hsv_01(hsl2rgb_01(col_hsl));
		o.pane1.colorEventY = clamp01(col_hsl[hsl_l]);
	}


  	o.pane1.begin_delta = o.pane2.begin_delta = begin_delta;
  	o.pane1.end_delta = o.pane2.end_delta = end_delta;
  	o.pane1.color=[0,0,0,1];
	o.on_change=function(){};
	o.render = palette_render;
  	o.set_color = set_color;
  	o.destroy = palette_destroy;
    return o;
}

function unipalette_pane1(id)
{
	var o = {};
	o.el = dge(id);
	if(!o.el) return;
	o.ctx = o.el.getContext('2d');
	o.colorEventX = o.colorEventY = 0.5;
	o.myoffset=get_location(o.el);
	push_handler(o.el, "onmousedown", palette_mousedown);
	push_handler(o.el, "onmouseup", palette_mouseup);

	function record_mouse(e){
	  o.colorEventX = clamp(0, (e.pageX - o.myoffset.left)/o.ctx.canvas.width, 1);
	  o.colorEventY = range_decompress(1-clamp(0, (e.pageY - o.myoffset.top)/o.ctx.canvas.height, 1));
	}
	function palette_mousedown(e){
	  push_handler(document, "onmousemove", palette_mousemove );
	  record_mouse(e);
	  o.begin_delta();
	}
	function palette_mousemove(e){
	  if(e.buttons==0) return o.el.onmouseup(e);
	  record_mouse(e);
	  stop_event(e);
	}
	function palette_mouseup(e){
	  pop_handler(document, "onmousemove");
	  o.end_delta();
	}
	function palette_modulate(rgb_col,sat)
	{
		var hsl_col=rgb2hsl(rgb_col);
		hsl_col[hsl_s]=sat;
		return hsl_out(hsl_col);
	}
	
	function palette_render(){
	  var ctx = o.ctx;
	  
	  var sat = o.other_pane.colorEventX;
	  var gradient = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
	  gradient.addColorStop(0,    palette_modulate( rgb_init(255,0,0), sat) /*"rgb(255,   0,   0)"*/ );
	  gradient.addColorStop(0.15, palette_modulate( rgb_init(255, 255, 0), sat));
	  gradient.addColorStop(0.33, palette_modulate( rgb_init(0, 255, 0), sat));
	  gradient.addColorStop(0.49, palette_modulate( rgb_init(0, 255, 255), sat));
	  gradient.addColorStop(0.67, palette_modulate( rgb_init(0, 0, 255), sat));
	  gradient.addColorStop(0.84, palette_modulate( rgb_init(255, 0, 255), sat));
	  gradient.addColorStop(1,    palette_modulate( rgb_init(255, 0, 0), sat));
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	  
	  var gradient = ctx.createLinearGradient(0,ctx.canvas.height/2,0,0);
	  gradient.addColorStop(0,    "rgba(255,255,255, 0)");
	  gradient.addColorStop(0.7,  "rgba(255,255,255, 0.5)");
	  gradient.addColorStop(0.9,  "rgba(255,255,255, 0.7)");
	  gradient.addColorStop(1,    "rgba(255,255,255, 1)");
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height/2);
	  	  
	  gradient = ctx.createLinearGradient(0,ctx.canvas.height/2,0,ctx.canvas.height);
	  gradient.addColorStop(0, 	  "rgba(0,0,0, 0)");
	  gradient.addColorStop(0.7,  "rgba(0,0,0, 0.5)");
	  gradient.addColorStop(0.9,  "rgba(0,0,0, 0.7)");
	  gradient.addColorStop(1,    "rgba(0,0,0, 1)");
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, ctx.canvas.height/2, ctx.canvas.width, ctx.canvas.height);

	  var r=7;
	  var x = o.colorEventX;
	  var y = range_compress(o.colorEventY);

	  ctx.beginPath();
	  ctx.arc(x*ctx.canvas.width, (1-y)*ctx.canvas.height, r, 0, 2 * Math.PI, false);
	  ctx.lineWidth = 1;
	  ctx.strokeStyle = 'white';
	  ctx.stroke();
	}
	o.render = palette_render;
	return o;
}

function unipalette_pane2(id)
{
	var o = {};
	o.el = dge(id);
	if(!o.el) return;
	o.ctx = o.el.getContext('2d');
	o.colorEventX = o.colorEventY = 0.5;
	o.myoffset=get_location(o.el);
	push_handler(o.el, "onmousedown", palette_mousedown);
	push_handler(o.el, "onmouseup", palette_mouseup);

	function record_mouse(e){
	  o.colorEventX = clamp(0, (e.pageX - o.myoffset.left)/o.ctx.canvas.width, 1);
	}
	function palette_mousedown(e){
	  push_handler(document, "onmousemove", palette_mousemove );
	  record_mouse(e);
	  o.begin_delta();
	}
	function palette_mousemove(e){
	  if(e.buttons==0) return o.el.onmouseup(e);
	  record_mouse(e);
	  stop_event(e);
	}
	function palette_mouseup(e){
	  pop_handler(document, "onmousemove");
	  o.end_delta();
	}
	function palette_render(){
	  var ctx = o.ctx;
	  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	  var gradient = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);

	  var col = o.base_color;
	  var low_sat = hsl_init(col[hsl_h], 0, 0.5);
	  var high_sat = hsl_init(col[hsl_h], 1, 0.5);
	  gradient.addColorStop(0, hsl_out(low_sat));
	  gradient.addColorStop(1, hsl_out(high_sat));
	  ctx.fillStyle = gradient;
	  ctx.fillRect(0, +5, ctx.canvas.width, ctx.canvas.height-10);

	  var r=7;
	  var x = o.colorEventX;

	  ctx.beginPath();
	  ctx.rect(x*ctx.canvas.width-0.5*r,ctx.canvas.height/2-1.5*r,r,2*1.5*r);
	  ctx.lineWidth = 2;
	  ctx.strokeStyle = 'black';
	  ctx.stroke();
	}
	o.render = palette_render;
	return o;
}
