xserver-multidpi/Xprint/pcl/PclColor.c

853 lines
21 KiB
C

/* $Xorg: PclColor.c,v 1.3 2000/08/17 19:48:07 cpqbld Exp $ */
/*******************************************************************
**
** *********************************************************
** *
** * File: PclColorInit.c
** *
** * Contents:
** * Colormap handing code of Pcl driver for the
** * print server.
** *
** * Created: 4/8/96
** *
** *********************************************************
**
********************************************************************/
/*
(c) Copyright 1996 Hewlett-Packard Company
(c) Copyright 1996 International Business Machines Corp.
(c) Copyright 1996 Sun Microsystems, Inc.
(c) Copyright 1996 Novell, Inc.
(c) Copyright 1996 Digital Equipment Corp.
(c) Copyright 1996 Fujitsu Limited
(c) Copyright 1996 Hitachi, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the names of the copyright holders shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in this Software without prior written authorization from said
copyright holders.
*/
/* $XFree86: xc/programs/Xserver/Xprint/pcl/PclColor.c,v 1.9 2001/10/28 03:32:54 tsi Exp $ */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <math.h>
#include "colormapst.h"
#include "windowstr.h"
#include "resource.h"
#include "Pcl.h"
#include "cfb.h"
static void lookup(unsigned char *src,
unsigned char *dst,
int num,
unsigned char *map,
int dim);
static void trilinear(unsigned char *p,
unsigned char *out,
unsigned char *d,
int dim,
unsigned char def);
/*
* This seems to be (and is) a duplication of effort; one would think
* that cfbCreateDefColormap would be sufficient. It almost is. The
* only change made in this function is that the black and white pixels
* are allocated with three separate variables for red, green and blue
* values, instead of the single variable in cfbCreateDefColormap. The
* single variable leads to the one value being corrected by
* ResolveColor three times, which leads to incorrect colors.
*/
Bool
PclCreateDefColormap(ScreenPtr pScreen)
{
unsigned short wp_red = ~0, wp_green = ~0, wp_blue = ~0;
unsigned short bp_red = 0, bp_green = 0, bp_blue = 0;
VisualPtr pVisual;
ColormapPtr cmap;
Pixel wp, bp;
for (pVisual = pScreen->visuals;
pVisual->vid != pScreen->rootVisual;
pVisual++)
;
if (CreateColormap(pScreen->defColormap, pScreen, pVisual, &cmap,
(pVisual->class & DynamicClass) ? AllocNone : AllocAll,
0)
!= Success)
return FALSE;
wp = pScreen->whitePixel;
bp = pScreen->blackPixel;
if ((AllocColor(cmap, &wp_red, &wp_green, &wp_blue, &wp, 0) !=
Success) ||
(AllocColor(cmap, &bp_red, &bp_green, &bp_blue, &bp, 0) !=
Success))
return FALSE;
pScreen->whitePixel = wp;
pScreen->blackPixel = bp;
(*pScreen->InstallColormap)(cmap);
return TRUE;
}
/*
* Add colormap to list of colormaps on screen
*/
Bool
PclCreateColormap(ColormapPtr pColor)
{
PclCmapToContexts *new;
PclScreenPrivPtr sPriv;
sPriv = (PclScreenPrivPtr)pColor->pScreen
->devPrivates[PclScreenPrivateIndex].ptr;
/*
* Use existing code to initialize the values in the colormap
*/
cfbInitializeColormap( pColor );
/*
* Set up the mapping between the color map and the context
*/
new = (PclCmapToContexts *)xalloc( sizeof( PclCmapToContexts ) );
if( new )
{
new->colormapId = pColor->mid;
new->contexts = NULL;
new->next = sPriv->colormaps;
sPriv->colormaps = new;
return TRUE;
}
else
return FALSE;
}
void
PclDestroyColormap(ColormapPtr pColor)
{
PclScreenPrivPtr sPriv;
PclCmapToContexts *pCmap, *tCmap = 0;
PclContextListPtr con, tCon;
PclContextPrivPtr cPriv;
PclPaletteMapPtr pPal;
char t[80];
/*
* At DestroyContext time, colormaps may be destroyed twice, so if the
* pointer is NULL, just crash out.
*/
if( !pColor )
return;
/*
* Find the colormap <-> contexts mapping
*/
sPriv = (PclScreenPrivPtr)pColor->pScreen
->devPrivates[PclScreenPrivateIndex].ptr;
pCmap = sPriv->colormaps;
while( pCmap )
{
if( pCmap->colormapId == pColor->mid )
break;
tCmap = pCmap;
pCmap = pCmap->next;
}
/*
* For each context, delete the palette in the printer and
* free the mapping.
*/
if( pCmap )
{
con = pCmap->contexts;
while( con )
{
cPriv = con->context->devPrivates[PclContextPrivateIndex].ptr;
pPal = cPriv->palettes;
while( pPal )
{
if( pPal->colormapId == pColor->mid )
break;
pPal = pPal->next;
}
if( cPriv->pPageFile )
{
sprintf( t, "\033&p%dI\033*p2C", pPal->paletteId );
SEND_PCL( cPriv->pPageFile, t );
}
tCon = con;
con = con->next;
xfree( tCon );
}
/*
* Delete the colormap<->contexts mapping
*/
if( sPriv->colormaps == pCmap )
/* Delete from the front */
sPriv->colormaps = pCmap->next;
else
/* Delete from the middle */
tCmap->next = pCmap->next;
free( pCmap );
}
}
void
PclInstallColormap(ColormapPtr pColor)
{
}
void
PclUninstallColormap(ColormapPtr pColor)
{
}
int
PclListInstalledColormaps(ScreenPtr pScreen,
XID *pCmapList)
{
return 0;
}
void
PclStoreColors(ColormapPtr pColor,
int ndef,
xColorItem *pdefs)
{
PclCmapToContexts *p;
PclScreenPrivPtr sPriv;
PclContextListPtr con;
PclContextPrivPtr cPriv;
PclPaletteMapPtr pMap;
char t[80];
int i;
sPriv = (PclScreenPrivPtr)pColor->pScreen
->devPrivates[PclScreenPrivateIndex].ptr;
p = sPriv->colormaps;
while( p )
{
if( p->colormapId == pColor->mid )
break;
p = p->next;
}
if( p )
{
con = p->contexts;
while( con )
{
/*
* For each context, get the palette ID and update the
* appropriate palette.
*/
cPriv = con->context
->devPrivates[PclContextPrivateIndex].ptr;
pMap = PclFindPaletteMap( cPriv, pColor, NULL );
/*
* Update the palette
*/
sprintf( t, "\033&p%dS", pMap->paletteId );
SEND_PCL( cPriv->pPageFile, t );
if( pColor->class == PseudoColor )
{
unsigned short r, g, b;
unsigned int pID;
for( i = 0; i < ndef; i++ )
{
pID = pdefs[i].pixel;
if ( pColor->red[i].fShared )
{
r = pColor->red[pID].co.shco.red->color;
g = pColor->red[pID].co.shco.green->color;
b = pColor->red[pID].co.shco.blue->color;
}
else
{
r = pColor->red[pID].co.local.red;
g = pColor->red[pID].co.local.green;
b = pColor->red[pID].co.local.blue;
}
if( pdefs[i].flags & DoRed )
r = pdefs[i].red;
if( pdefs[i].flags & DoGreen )
g = pdefs[i].green;
if( pdefs[i].flags & DoBlue )
b = pdefs[i].blue;
PclLookUp(pColor, cPriv, &r, &g, &b);
sprintf( t, "\033*v%ua%ub%uc%dI", r, g, b, pID);
SEND_PCL( cPriv->pPageFile, t );
}
}
sprintf( t, "\033&p%dS", cPriv->currentPalette );
SEND_PCL( cPriv->pPageFile, t );
con = con->next;
}
}
}
void
PclResolveColor(unsigned short *pRed,
unsigned short *pGreen,
unsigned short *pBlue,
VisualPtr pVisual)
{
/*
* We need to map the X color range of [0,65535] to the PCL color
* range of [0,32767].
*/
*pRed >>= 1;
*pGreen >>= 1;
*pBlue >>= 1;
}
PclPaletteMapPtr
PclFindPaletteMap(PclContextPrivPtr cPriv,
ColormapPtr cmap,
GCPtr gc)
{
PclPaletteMapPtr p = cPriv->palettes, new;
/*
* If the colormap is static, grab one of the special palettes. If we come
* into this from StoreColors, there will be no GC, but by definition we're
* looking at a dynamic color map, so the special colors will not be
* needed.
*/
if( gc )
{
if( cmap->pVisual->class == StaticGray )
return &( cPriv->staticGrayPalette );
else if( cmap->pVisual->class == TrueColor )
{
if( gc->fillStyle == FillTiled && !( gc->tileIsPixel ) )
return &( cPriv->specialTrueColorPalette );
else
return &( cPriv->trueColorPalette );
}
}
/* Look for the colormap ID <-> palette ID mapping */
while( p )
{
if( p->colormapId == cmap->mid )
return p;
p = p->next;
}
/* If the colormap isn't already there, make an entry for it */
new = (PclPaletteMapPtr)xalloc( sizeof( PclPaletteMap ) );
new->colormapId = cmap->mid;
new->paletteId = cPriv->nextPaletteId++;
new->downloaded = 0;
new->next = cPriv->palettes;
cPriv->palettes = new;
return new;
}
int
PclUpdateColormap(DrawablePtr pDrawable,
XpContextPtr pCon,
GCPtr gc,
FILE *outFile)
{
PclScreenPrivPtr sPriv;
PclContextPrivPtr cPriv;
PclPaletteMapPtr pMap;
PclCmapToContexts *pCmap, *tCmap;
PclContextListPtr new;
char t[80];
Colormap c;
ColormapPtr cmap;
WindowPtr win = (WindowPtr)pDrawable;
unsigned short r, g, b, rr, gg, bb;
int i;
cPriv = pCon->devPrivates[PclContextPrivateIndex].ptr;
c = wColormap( win );
cmap = (ColormapPtr)LookupIDByType( c, RT_COLORMAP );
pMap = PclFindPaletteMap( cPriv, cmap, gc );
if( cPriv->currentPalette == pMap->paletteId )
/*
* If the requested colormap is already active, nothing needs to
* be done.
*/
return FALSE;
/*
* Now we activate the palette in the printer
*/
sprintf( t, "\033&p%dS", pMap->paletteId );
SEND_PCL( outFile, t );
cPriv->currentPalette = pMap->paletteId;
if( pMap->downloaded == 0 )
/*
* If the requested colormap has not been downloaded to the
* printer, we need to do that before activating it.
*/
{
/*
* Add the colormap to the screen-level colormap<->context mapping.
*/
sPriv = (PclScreenPrivPtr)cmap->pScreen
->devPrivates[PclScreenPrivateIndex].ptr;
pCmap = sPriv->colormaps;
while( pCmap )
{
if( pCmap->colormapId == cmap->mid )
break;
tCmap = pCmap;
pCmap = pCmap->next;
}
new = (PclContextListPtr)xalloc( sizeof( PclContextList ) );
new->context = pCon;
new->next = pCmap->contexts;
pCmap->contexts = new;
/*
* XXX Download the colormap
*/
if( cmap->class == StaticGray )
{
#ifdef XP_PCL_COLOR
sprintf( t, "\033*v18W%c%c%c%c%c%c", 0, 1, 1, 1, 1, 1 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 12 );
/* Send the white reference point... */
sprintf( t, "%c%c%c%c%c%c", 0x7f, 0xff, 0x7f, 0xff,
0x7f, 0xff );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* ... and the black reference point */
sprintf( t, "%c%c%c%c%c%c", 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* Now program the two colors */
sprintf( t, "\033*v0a0b0c%ldI", (long) cmap->pScreen->blackPixel );
SEND_PCL( cPriv->pPageFile, t );
sprintf( t, "\033*v32767a32767b32767c%ldI",
(long) cmap->pScreen->whitePixel );
SEND_PCL( cPriv->pPageFile, t );
#endif /* XP_PCL_COLOR */
}
else if( cmap->class == PseudoColor )
{
sprintf( t,
"\033*v18W%c%c%c%c%c%c",
0, 1, cmap->pVisual->nplanes, 16, 16, 16 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 12 );
/* Send the white reference point... */
if ( cPriv->ctbl != NULL )
sprintf( t, "%c%c%c%c%c%c", 0x00, 0xff, 0x00, 0xff,
0x00, 0xff );
else
sprintf( t, "%c%c%c%c%c%c", 0x7f, 0xff, 0x7f, 0xff,
0x7f, 0xff );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* ... and the black reference point */
sprintf( t, "%c%c%c%c%c%c", 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
for(i = 0; i < cmap->pVisual->ColormapEntries; i++ )
{
if( cmap->red[i].fShared )
{
r = cmap->red[i].co.shco.red->color;
g = cmap->red[i].co.shco.green->color;
b = cmap->red[i].co.shco.blue->color;
}
else
{
r = cmap->red[i].co.local.red;
g = cmap->red[i].co.local.green;
b = cmap->red[i].co.local.blue;
}
PclLookUp(cmap, cPriv, &r, &g, &b);
sprintf( t, "\033*v%ua%ub%uc%dI", r, g, b, i );
SEND_PCL( outFile, t );
}
}
else if( cmap->class == TrueColor )
{
unsigned short lim;
if( gc->fillStyle == FillTiled && !( gc->tileIsPixel ) )
{
if( cPriv->ctbl != NULL )
{
/* Send the "special" colormap for 24-bit fills */
sprintf( t, "\033*v18W%c%c%c%c%c%c", 0, 1,
8,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue );
SEND_PCL_COUNT( cPriv->pPageFile, t, 12 );
/* Send the white reference point... */
sprintf( t, "%c%c%c%c%c%c",
0x00, 0xff,
0x00, 0xff,
0x00, 0xff );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* ... and the black reference point */
sprintf( t, "%c%c%c%c%c%c",
0x00, 0x00,
0x00, 0x00,
0x00, 0x00 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* Now send the color entries, RRRGGGBB */
i=0;
for( r = 0; r < 8; r++ )
for( g = 0; g < 8; g ++ )
for( b = 0; b < 4; b++ )
{
rr = (r * 0xff)/7;
gg = (g * 0xff)/7;
bb = (b * 0xff)/3;
PclLookUp(cmap, cPriv, &rr, &gg, &bb);
sprintf( t, "\033*v%ua%ub%uc%dI",
rr, gg, bb, i );
SEND_PCL( outFile, t );
i++;
}
}
else
{
/* Send the "special" colormap for 24-bit fills */
sprintf( t, "\033*v18W%c%c%c%c%c%c", 0, 1,
8,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue );
SEND_PCL_COUNT( cPriv->pPageFile, t, 12 );
/* Send the white reference point... */
sprintf( t, "%c%c%c%c%c%c",
0x00, 0x07,
0x00, 0x07,
0x00, 0x03 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* ... and the black reference point */
sprintf( t, "%c%c%c%c%c%c",
0x00, 0x00,
0x00, 0x00,
0x00, 0x00 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* Now send the color entries, RRRGGGBB */
i=0;
for( r = 0; r < 8; r++ )
for( g = 0; g < 8; g ++ )
for( b = 0; b < 4; b++ )
{
sprintf( t, "\033*v%ua%ub%uc%dI",
r, g, b, i );
SEND_PCL( outFile, t );
i++;
}
}
}
else
{
lim = (1 << cmap->pVisual->bitsPerRGBValue) - 1;
/* Send the "special" colormap for 24-bit fills */
sprintf( t, "\033*v18W%c%c%c%c%c%c", 0, 3,
cmap->pVisual->nplanes,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue,
cmap->pVisual->bitsPerRGBValue );
SEND_PCL_COUNT( cPriv->pPageFile, t, 12 );
/* Send the white reference point... */
sprintf( t, "%c%c%c%c%c%c",
(lim >> 8) & 0xff, lim & 0xff,
(lim >> 8) & 0xff, lim & 0xff,
(lim >> 8) & 0xff, lim & 0xff);
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
/* ... and the black reference point */
sprintf( t, "%c%c%c%c%c%c", 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 );
SEND_PCL_COUNT( cPriv->pPageFile, t, 6 );
}
}
pMap->downloaded = 1;
}
return TRUE;
}
void PclLookUp(
ColormapPtr cmap,
PclContextPrivPtr cPriv,
unsigned short *r,
unsigned short *g,
unsigned short *b
)
{
unsigned char cdata[3];
if( cmap->class == PseudoColor )
{
if( cPriv->ctbl != NULL )
{
cdata[0] = *r >> 8;
cdata[1] = *g >> 8;
cdata[2] = *b >> 8;
lookup(cdata, cdata, 1, cPriv->ctbl, cPriv->ctbldim);
*r = cdata[0];
*g = cdata[1];
*b = cdata[2];
}
else
{
*r >>= 1;
*g >>= 1;
*b >>= 1;
}
}
else if( cmap->class == TrueColor )
{
if( cPriv->ctbl != NULL )
{
cdata[0] = *r;
cdata[1] = *g;
cdata[2] = *b;
lookup(cdata, cdata, 1, cPriv->ctbl, cPriv->ctbldim);
*r = cdata[0];
*g = cdata[1];
*b = cdata[2];
}
}
return;
}
unsigned char *PclReadMap(char *name, int *dim)
{
FILE *fp;
unsigned char *data;
long size;
if ((fp=fopen(name, "r")) == NULL) {
return(NULL);
}
fseek(fp, 0, SEEK_END);
size = ftell(fp);
/* Could do this with a lookup table, if the constraint is that the
3 map dimensions must be equal. */
switch (size) {
case 8*8*8*3:
*dim = 8;
break;
case 16*16*16*3:
*dim = 16;
break;
case 17*17*17*3:
*dim = 17;
break;
case 65*65*65*3:
*dim = 65;
break;
default:
fclose(fp);
return(NULL);
}
if ((data = (unsigned char *) xalloc(sizeof(char) * size)) == NULL) {
fclose(fp);
return(NULL);
}
fseek(fp, 0, SEEK_SET);
if (fread(data, sizeof(char), size, fp) != (unsigned) size) {
fclose(fp);
free(data);
return(NULL);
}
fclose(fp);
return(data);
}
/************************************************************************
*
* Here is the mapper.
*
************************************************************************/
#define SCL(x) ((x)*(dim-1)/255)
/* Interleaved-map lookup */
static void lookup(unsigned char *src, unsigned char *dst, int num, unsigned char *map, int dim)
{
int i;
#define _INTERPOLATE
#ifndef _INTERPOLATE
unsigned char *p1, *p2, *p3;
for (i=0; i<num; i++) {
p1 = map + (SCL(src[0])*dim*dim + SCL(src[1])*dim + SCL(src[2])) * 3;
*dst++ = *p1++;
*dst++ = *p1++;
*dst++ = *p1++;
src += 3;
}
#else
for (i=0; i<num; i++) {
trilinear(src, dst, map, dim, 128);
src += 3;
dst += 3;
}
#endif
}
/*
* C code from the article
* "Tri-linear Interpolation"
* by Steve Hill, sah@ukc.ac.uk
* in "Graphics Gems IV", Academic Press, 1994
*
* Fri Feb 16 14:12:43 PST 1996
* Modified to use for 8-bit color mapping -- A. Fitzhugh,
* HP Labs, Printing Technology Department
*/
/* linear interpolation from l (when a=0) to h (when a=1)*/
/* (equal to (a*h)+((1-a)*l) */
#define LERP(a,l,h) ((l)+((((h)-(l))*(a))>>8))
static void trilinear(unsigned char *p, unsigned char *out, unsigned char *d, int dim, unsigned char def)
{
#define DENS(X, Y, Z, ch) d[((X*dim+Y)*dim+Z)*3+ch]
int x0, y0, z0,
x1, y1, z1,
i;
unsigned char *dp,
fx, fy, fz,
d000, d001, d010, d011,
d100, d101, d110, d111,
dx00, dx01, dx10, dx11,
dxy0, dxy1;
float scale;
scale = 255.0 / (dim-1);
x0 = p[0] / scale;
y0 = p[1] / scale;
z0 = p[2] / scale;
/* Fractions should range from 0-1.0 (fixed point 8-256) */
fx = (((int) (p[0] - x0 * scale)) << 8) / 255;
fy = (((int) (p[1] - y0 * scale)) << 8) / 255;
fz = (((int) (p[2] - z0 * scale)) << 8) / 255;
x1 = x0 + 1;
y1 = y0 + 1;
z1 = z0 + 1;
for (i=0; i<3; i++) {
if (x0 >= 0 && x1 < dim &&
y0 >= 0 && y1 < dim &&
z0 >= 0 && z1 < dim) {
dp = &DENS(x0, y0, z0, i);
d000 = dp[0];
d100 = dp[3];
dp += dim*3;
d010 = dp[0];
d110 = dp[3];
dp += dim*dim*3;
d011 = dp[0];
d111 = dp[3];
dp -= dim*3;
d001 = dp[0];
d101 = dp[3];
} else {
# define INRANGE(X, Y, Z) \
((X) >= 0 && (X) < dim && \
(Y) >= 0 && (Y) < dim && \
(Z) >= 0 && (Z) < dim)
d000 = INRANGE(x0, y0, z0) ? DENS(x0, y0, z0, i) : def;
d001 = INRANGE(x0, y0, z1) ? DENS(x0, y0, z1, i) : def;
d010 = INRANGE(x0, y1, z0) ? DENS(x0, y1, z0, i) : def;
d011 = INRANGE(x0, y1, z1) ? DENS(x0, y1, z1, i) : def;
d100 = INRANGE(x1, y0, z0) ? DENS(x1, y0, z0, i) : def;
d101 = INRANGE(x1, y0, z1) ? DENS(x1, y0, z1, i) : def;
d110 = INRANGE(x1, y1, z0) ? DENS(x1, y1, z0, i) : def;
d111 = INRANGE(x1, y1, z1) ? DENS(x1, y1, z1, i) : def;
}
dx00 = LERP(fx, d000, d100);
dx01 = LERP(fx, d001, d101);
dx10 = LERP(fx, d010, d110);
dx11 = LERP(fx, d011, d111);
dxy0 = LERP(fy, dx00, dx10);
dxy1 = LERP(fy, dx01, dx11);
out[i] = LERP(fz, dxy0, dxy1);
}
}