using range=bounds(picture pic, real min, real max);
range Range(bool automin=false, real min=-infinity,
bool automax=false, real max=infinity)
{
return new bounds(picture pic, real dmin, real dmax) {
// autoscale routine finds reasonable limits
bounds mz=autoscale(pic.scale.z.T(dmin),
pic.scale.z.T(dmax),
pic.scale.z.scale);
// If automin/max, use autoscale result, else
// if min/max is finite, use specified value, else
// use minimum/maximum data value
real pmin=automin ? pic.scale.z.Tinv(mz.min) : (finite(min) ? min : dmin);
real pmax=automax ? pic.scale.z.Tinv(mz.max) : (finite(max) ? max : dmax);
return bounds(pmin,pmax);
};
}
range Automatic=Range(true,true);
range Full=Range();
// Reduce color palette to approximate range of data relative to "display"
// range => errors of 1/palette.length in resulting color space.
pen[] adjust(picture pic, real min, real max, real rmin, real rmax,
pen[] palette)
{
real dmin=pic.scale.z.T(min);
real dmax=pic.scale.z.T(max);
real delta=rmax-rmin;
if(delta > 0) {
real factor=palette.length/delta;
int minindex=floor(factor*(dmin-rmin));
if(minindex < 0) minindex=0;
int maxindex=ceil(factor*(dmax-rmin));
if(maxindex > palette.length) maxindex=palette.length;
if(minindex > 0 || maxindex < palette.length)
return palette[minindex:maxindex];
}
return palette;
}
bounds bounds=range(pic,m,M);
real rmin=pic.scale.z.T(bounds.min);
real rmax=pic.scale.z.T(bounds.max);
palette=adjust(pic,m,M,rmin,rmax,palette);
// Crop data to allowed range and scale
if(range != Full || pic.scale.z.scale.T != identity ||
pic.scale.z.postscale.T != identity) {
scalefcn T=pic.scale.z.T;
real m=bounds.min;
real M=bounds.max;
for(int i=0; i < f.length; ++i)
f[i]=map(new real(real x) {return T(min(max(x,m),M));},f[i]);
}
if(divs > 0) {
int nx=f.length;
int ny=f[0].length;
real[][] data=new real[nx][ny];
real incr=(M-m)/divs;
for(int i=0; i < nx; ++i) {
// Take center point of each bin
real[] fi=f[i];
data[i]=sequence(new real(int j) {
return min(m+floor((fi[j]-m)/incr)*incr,M-incr);
},ny);
}
f=data;
}
pic.add(new void(frame F, transform t) {
_image(F,f,initial,final,palette,t*T,copy=false,antialias=antialias);
},true);
return bounds; // Return bounds used for color space
}
bounds image(picture pic=currentpicture, real f(real, real),
range range=Full, pair initial, pair final,
int nx=ngraph, int ny=nx, pen[] palette, int divs=0,
bool antialias=false)
{
// Generate data, taking scaling into account
real xmin=pic.scale.x.T(initial.x);
real xmax=pic.scale.x.T(final.x);
real ymin=pic.scale.y.T(initial.y);
real ymax=pic.scale.y.T(final.y);
real[][] data=new real[ny][nx];
for(int j=0; j < ny; ++j) {
real y=pic.scale.y.Tinv(interp(ymin,ymax,(j+0.5)/ny));
scalefcn Tinv=pic.scale.x.Tinv;
// Take center point of each bin
data[j]=sequence(new real(int i) {
return f(Tinv(interp(xmin,xmax,(i+0.5)/nx)),y);
},nx);
}
bounds image(picture pic=currentpicture, pair[] z, real[] f,
range range=Full, pen[] palette)
{
if(z.length != f.length)
abort("z and f arrays have different lengths");
real m=min(f);
real M=max(f);
bounds bounds=range(pic,m,M);
real rmin=pic.scale.z.T(bounds.min);
real rmax=pic.scale.z.T(bounds.max);
// Crop data to allowed range and scale
if(range != Full || pic.scale.z.scale.T != identity ||
pic.scale.z.postscale.T != identity) {
scalefcn T=pic.scale.z.T;
real m=bounds.min;
real M=bounds.max;
f=map(new real(real x) {return T(min(max(x,m),M));},f);
}
if(pic.scale.x.scale.T != identity || pic.scale.x.postscale.T != identity ||
pic.scale.y.scale.T != identity || pic.scale.y.postscale.T != identity) {
scalefcn Tx=pic.scale.x.T;
scalefcn Ty=pic.scale.y.T;
z=map(new pair(pair z) {return (Tx(z.x),Ty(z.y));},z);
}
int[] edges={0,0,1};
int N=palette.length-1;
int[][] trn=triangulate(z);
real step=rmax == rmin ? 0.0 : N/(rmax-rmin);
for(int i=0; i < trn.length; ++i) {
int[] trni=trn[i];
int i0=trni[0], i1=trni[1], i2=trni[2];
pen color(int i) {return palette[round((f[i]-rmin)*step)];}
gouraudshade(pic,z[i0]--z[i1]--z[i2]--cycle,
new pen[] {color(i0),color(i1),color(i2)},edges);
}
return bounds; // Return bounds used for color space
}
bounds image(picture pic=currentpicture, real[] x, real[] y, real[] f,
range range=Full, pen[] palette)
{
int n=x.length;
if(n != y.length)
abort("x and y arrays have different lengths");
// Construct a pen[] array from f using the specified palette.
pen[] palette(real[] f, pen[] palette)
{
real Min=min(f);
real Max=max(f);
if(palette.length == 0) return new pen[];
real step=Max == Min ? 0.0 : (palette.length-1)/(Max-Min);
return sequence(new pen(int i) {return palette[round((f[i]-Min)*step)];},
f.length);
}
// Construct a pen[][] array from f using the specified palette.
pen[][] palette(real[][] f, pen[] palette)
{
real Min=min(f);
real Max=max(f);
int n=f.length;
pen[][] p=new pen[n][];
real step=(Max == Min) ? 0.0 : (palette.length-1)/(Max-Min);
for(int i=0; i < n; ++i) {
real[] fi=f[i];
p[i]=sequence(new pen(int j) {return palette[round((fi[j]-Min)*step)];},
f[i].length);
}
return p;
}
typedef ticks paletteticks(int sign=-1);
paletteticks PaletteTicks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
int N=0, int n=0, real Step=0, real step=0,
pen pTick=nullpen, pen ptick=nullpen)
{
return new ticks(int sign=-1) {
format.align(sign > 0 ? RightSide : LeftSide);
return Ticks(sign,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
true,true,extend=true,pTick,ptick);
};
}
int Nintervals=nintervals*divisor;
if(NColors <= Nintervals) NColors=Nintervals+1;
int num=NColors-offset;
int n=-quotient(num,-Nintervals)*divisor;
NColors=n*nintervals+offset;
pen[] Palette;
Palette=new pen[NColors];
real ninv=1.0/n;
int k=0;
if(two) {
for(int i=0; i < n; ++i) {
real ininv=i*ninv;
real ininv1=1.0-ininv;
Palette[i]=rgb(ininv1,0.0,1.0);
Palette[n+i]=rgb(0.0,ininv,1.0);
Palette[2n+i]=rgb(0.0,1.0,ininv1);
Palette[3n+i]=rgb(ininv,1.0,0.0);
Palette[4n+i]=rgb(1.0,ininv1,0.0);
Palette[5n+i]=rgb(1.0,0.0,ininv);
}
k += 6n;
}
if(two)
for(int i=0; i < n; ++i)
Palette[k+i]=rgb(1.0-i*ninv,0.0,1.0);
else {
int n3=-quotient(n,-3);
int n23=2*n3;
real third=n3*ninv;
real twothirds=n23*ninv;
for(int i=0; i < n3; ++i) {
real ininv=i*ninv;
Palette[k+i]=rgb(ininv,0.0,ininv);
Palette[k+n3+i]=rgb(third,0.0,third+ininv);
Palette[k+n23+i]=rgb(third-ininv,0.0,twothirds+ininv);
}
}
k += n;
for(int i=0; i < n; ++i) {
real ininv=i*ninv;
real ininv1=1.0-ininv;
Palette[k+i]=rgb(0.0,ininv,1.0);
Palette[k+n+i]=rgb(0.0,1.0,ininv1);
Palette[k+2n+i]=rgb(ininv,1.0,0.0);
Palette[k+3n+i]=rgb(1.0,ininv1,0.0);
Palette[k+4n+i]=rgb(1.0,ininv,ininv);
}
Palette[k+5n]=rgb(1.0,1.0,1.0);
return Palette;
}
// Quantize palette to exactly n values
pen[] quantize(pen[] Palette, int n)
{
if(Palette.length == 0) abort("cannot quantize empty palette");
if(n <= 1) abort("palette must contain at least two pens");
real step=(Palette.length-1)/(n-1);
return sequence(new pen(int i) {
return Palette[round(i*step)];
},n);
}
// A rainbow palette tapering off to black/white at the spectrum ends,
pen[] BWRainbow(int NColors=32761)
{
return BWRainbow(NColors,false);
}
// A double rainbow palette tapering off to black/white at the spectrum ends,
// with a linearly scaled intensity.
pen[] BWRainbow2(int NColors=32761)
{
pen[] Palette=BWRainbow(NColors,true);
int n=Palette.length;
real ninv=1.0/n;
for(int i=0; i < n; ++i)
Palette[i]=i*ninv*Palette[i];
return Palette;
}
//A palette varying linearly over the specified array of pens, using
// NColors in each interpolation interval.
pen[] Gradient(int NColors=256 ... pen[] p)
{
pen[] P;
if(p.length < 2) abort("at least 2 colors must be specified");
real step=NColors > 1 ? (1/(NColors-1)) : 1;
for(int i=0; i < p.length-1; ++i) {
pen begin=p[i];
pen end=p[i+1];
P.append(sequence(new pen(int j) {
return interp(begin,end,j*step);
},NColors));
}
return P;
}
pen[] cmyk(pen[] Palette)
{
int n=Palette.length;
for(int i=0; i < n; ++i)
Palette[i]=cmyk(Palette[i]);
return Palette;
}