/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_CompFast.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Image compositing functions, faster but limited version.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_CompFastAlpha
 *                      IP_CompFastColorMatte
 *                      IP_CompFastDepth
 *                      IP_CrossDissolveFast
 *
 **************************************************************************/

#include <stdio.h>
#include <math.h>

#include "UT_Compat.h"
#include "UT_Macros.h"
#include "UT_Error.h"
#include "UT_Parallel.h"

#define USE_IMG_MACROS
#include "FF_Image.h"

#include "IP_Image.h"
#include "IP_ImagePrivate.h"


/* Parameters for the "conc_comp_bwmatte" function */

typedef struct {
        IP_ImageId      fgimg,          /* Foreground image */
                        bgimg,          /* Background image */
                        cmimg,          /* Composited image */
                        mtimg;          /* Matte image */
        Int32           width,          /* Dimensions of compositing area */
                        height;
        } bwmparms;



/* Parameters for the "conc_comp_clrmatte" function */

typedef struct {
        IP_ImageId      fgimg,          /* Foreground image */
                        bgimg,          /* Background image */
                        cmimg,          /* Composited image */
                        mtimg;          /* Matte image */
        Int32           width,          /* Dimensions of compositing area */
                        height;
        } clrmparms;



/* Parameters for the "conc_comp_zmatte" function */

typedef struct {
        IP_ImageId      fgimg,          /* Foreground image */
                        bgimg,          /* Background image */
                        cmimg;          /* Composited image */
        Int32           width,          /* Dimensions of compositing area */
                        height;
        } zmparms;



/* Parameters for the "conc_crossimage" function */

typedef struct {
        IP_ImageId      fgimg,          /* Foreground image */
                        bgimg,          /* Background image */
                        cmimg;          /* Cross-dissolved image */
        Int32           width,          /* Dimensions of compositing area */
                        height;
        Float32         mix;            /* Background / foreground weight */
        } crossparms;



/* Amount of memory reserved for the concurrently
invoked functions' auto variable stacks */

#define stacksize       (1 << 12)



/***************************************************************************
 *
 *      Gamma correction and matting lookup tables
 *
 ***************************************************************************/

/* Lookup table resolution; the entries in "lutb_r", "lutb_g", "lutb_g" etc.
are always in the range from 0 to (lutres - 1). */

#define lutres          (1 << 14)
#define lutmax          (lutres - 1)



/* Lookup tables for background pixel data scaling without matte */

static volatile Float32 gammabg_r = -1.0, scalebg_r = -1.0,
                        gammabg_g = -1.0, scalebg_g = -1.0,
                        gammabg_b = -1.0, scalebg_b = -1.0;

static Int16            lutb_r[256],
                        lutb_g[256],
                        lutb_b[256];

static const Int16      *lb_r = NULL,
                        *lb_g = NULL,
                        *lb_b = NULL;



/* Lookup tables for background pixel data scaling with matte */

static volatile Float32 gammamb_r = -1.0, scalemb_r = -1.0,
                        gammamb_g = -1.0, scalemb_g = -1.0,
                        gammamb_b = -1.0, scalemb_b = -1.0;

static Int16            lutmb_r[256 * 256],
                        lutmb_g[256 * 256],
                        lutmb_b[256 * 256];

static const Int16      *lmb_r = NULL,
                        *lmb_g = NULL,
                        *lmb_b = NULL;



/* Lookup tables for foreground pixel data scaling without matte */

static volatile Float32 gammafg_r = -1.0, scalefg_r = -1.0,
                        gammafg_g = -1.0, scalefg_g = -1.0,
                        gammafg_b = -1.0, scalefg_b = -1.0;

static Int16            lutf_r[256],
                        lutf_g[256],
                        lutf_b[256];

static const Int16      *lf_r = NULL,
                        *lf_g = NULL,
                        *lf_b = NULL;



/* Lookup tables for foreground pixel data scaling with matte */

static volatile Float32 gammamf_r = -1.0, scalemf_r = -1.0,
                        gammamf_g = -1.0, scalemf_g = -1.0,
                        gammamf_b = -1.0, scalemf_b = -1.0;

static Int16            lutmf_r[256 * 256],
                        lutmf_g[256 * 256],
                        lutmf_b[256 * 256];

static const Int16      *lmf_r = NULL,
                        *lmf_g = NULL,
                        *lmf_b = NULL;


/* Lookup table for composite-image gamma-correction */

static volatile Float32 gammac;

static UInt8            lc[2 * lutres];



/***************************************************************************
 *
 *      Lookup table setup
 *
 ***************************************************************************/

/* Store an approximation of the function f(x) = scale * pow (x, gamma)
   in table "lb_r", "lb_g" or "lb_b", depending on whether "chn" is equal to
   FF_ImgChanTypeRed, FF_ImgChanTypeGreen or FF_ImgChanTypeBlue. */

static void make_lb (FF_ImgChanType chn, Float32 gamma, Float32 scale)
{
    Int32   b, t;
    Float32 p;
    Int16   *table = NULL;

    switch (chn) {
        case FF_ImgChanTypeRed: {
            if (gamma == gammabg_r && scale == scalebg_r) {
                return;
            }
            table = lutb_r;
            break;
        }
        case FF_ImgChanTypeGreen: {
            if (gamma == gammabg_r && scale == scalebg_r) {
                lb_g = lutb_r;
                return;
            }
            if (gamma == gammabg_g && scale == scalebg_g) {
                return;
            }
            table = lutb_g;
            break;
        }
        case FF_ImgChanTypeBlue: {
            if (gamma == gammabg_r && scale == scalebg_r) {
                lb_b = lutb_r;
                return;
            }
            if (gamma == gammabg_g && scale == scalebg_g) {
                lb_b = lutb_g;
                return;
            }
            if (gamma == gammabg_b && scale == scalebg_b) {
                return;
            }
            table = lutb_b;
            break;
        }
        default: {
            UT_ErrFatal ("make_lb", "invalid channel type");
        }
    }

    for (b = 0; b < 256; ++b) {
        p = scale * pow (b * (1.0 / 255.0), gamma);
        t = (Int32)(lutmax * p + 0.5);
        table[b] = UT_MAX (0, UT_MIN (t, lutmax));
    }

    switch (chn) {
        case FF_ImgChanTypeRed: {
            gammabg_r = gamma;
            scalebg_r = scale;
            lb_r = table;
            break;
        }
        case FF_ImgChanTypeGreen: {
            gammabg_g = gamma;
            scalebg_g = scale;
            lb_g = table;
            break;
        }
        case FF_ImgChanTypeBlue: {
            gammabg_b = gamma;
            scalebg_b = scale;
            lb_b = table;
            break;
        }
        default: {
            UT_ErrFatal ("make_lb", "invalid channel type");
        }
    }
    return;
}



/* Store an approximation of function f(x, y) = scale * (1-y) * pow (x, gamma)
   in table "lmb_r", "lmb_g" or "lmb_b", depending on whether "chn" is equal to
   FF_ImgChanTypeRed, FF_ImgChanTypeGreen or FF_ImgChanTypeBlue. */

static void make_lmb (FF_ImgChanType chn, Float32 gamma, Float32 scale)
{
    Int32   m, b, t;
    Float32 p;
    Int16   *table = NULL;

    switch (chn) {
        case FF_ImgChanTypeRed: {
            if (gamma == gammamb_r && scale == scalemb_r) {
                return;
            }
            table = lutmb_r;
            break;
        }
        case FF_ImgChanTypeGreen: {
            if (gamma == gammamb_r && scale == scalemb_r) {
                lmb_g = lutmb_r;
                return;
            }
            if (gamma == gammamb_g && scale == scalemb_g) {
                return;
            }
            table = lutmb_g;
            break;
        }
        case FF_ImgChanTypeBlue: {
            if (gamma == gammamb_r && scale == scalemb_r) {
                lmb_b = lutmb_r;
                return;
            }
            if (gamma == gammamb_g && scale == scalemb_g) {
                lmb_b = lutmb_g;
                return;
            }
            if (gamma == gammamb_b && scale == scalemb_b) {
                return;
            }
            table = lutmb_b;
            break;
        }
        default: {
            UT_ErrFatal ("make_lmb", "invalid channel type");
        }
    }

    for (b = 0; b < 256; ++b) {
        p = scale * pow (b * (1.0 / 255.0), gamma);
        for (m = 0; m < 256; ++m) {
            t = (Int32)(lutmax * (1.0 - m * (1.0 / 255.0)) * p + 0.5);
            table[m + (b << 8)] = UT_MAX (0, UT_MIN (t, lutmax));
        }
    }

    switch (chn) {
        case FF_ImgChanTypeRed: {
            gammamb_r = gamma;
            scalemb_r = scale;
            lmb_r = table;
            break;
        }
        case FF_ImgChanTypeGreen: {
            gammamb_g = gamma;
            scalemb_g = scale;
            lmb_g = table;
            break;
        }
        case FF_ImgChanTypeBlue: {
            gammamb_b = gamma;
            scalemb_b = scale;
            lmb_b = table;
            break;
        }
        default: {
            UT_ErrFatal ("make_lmb", "invalid channel type");
        }
    }
    return;
}



/* Store an approximation of the function f(x) = scale * pow (x, gamma)
   in table "lf_r", "lf_g" or "lf_b", depending on whether "chn" is equal to
   FF_ImgChanTypeRed, FF_ImgChanTypeGreen or FF_ImgChanTypeBlue. */

static void make_lf (FF_ImgChanType chn, Float32 gamma, Float32 scale)
{
    Int32   f, t;
    Float32 p;
    Int16   *table = NULL;

    switch (chn) {
        case FF_ImgChanTypeRed: {
            if (gamma == gammafg_r && scale == scalefg_r) {
                return;
            }
            table = lutf_r;
            break;
        }
        case FF_ImgChanTypeGreen: {
            if (gamma == gammafg_r && scale == scalefg_r) {
                lf_g = lutf_r;
                return;
            }
            if (gamma == gammafg_g && scale == scalefg_g) {
                return;
            }
            table = lutf_g;
            break;
        }
        case FF_ImgChanTypeBlue: {
            if (gamma == gammafg_r && scale == scalefg_r) {
                lf_b = lutf_r;
                return;
            }
            if (gamma == gammafg_g && scale == scalefg_g) {
                lf_b = lutf_g;
                return;
            }
            if (gamma == gammafg_b && scale == scalefg_b) {
                return;
            }
            table = lutf_b;
            break;
        }
        default: {
            UT_ErrFatal ("make_lf", "invalid channel type");
        }
    }

    for (f = 0; f < 256; ++f) {
        p = scale * pow (f * (1.0 / 255.0), gamma);
        t = (Int32)(lutmax * p + 0.5);
        table[f] = UT_MAX (0, UT_MIN (t, lutmax));
    }

    switch (chn) {
        case FF_ImgChanTypeRed: {
            gammafg_r = gamma;
            scalefg_r = scale;
            lf_r = table;
            break;
        }
        case FF_ImgChanTypeGreen: {
            gammafg_g = gamma;
            scalefg_g = scale;
            lf_g = table;
            break;
        }
        case FF_ImgChanTypeBlue: {
            gammafg_b = gamma;
            scalefg_b = scale;
            lf_b = table;
            break;
        }
        default: {
            UT_ErrFatal ("make_lf", "invalid channel type");
        }
    }
    return;
}



/* Store an approximation of function f(x, y) = scale * y * pow (x, gamma)
   in table "lmf_r", "lmf_g" or "lmf_b", depending on whether "chn" is equal to
   FF_ImgChanTypeRed, FF_ImgChanTypeGreen or FF_ImgChanTypeBlue. */

static void make_lmf (FF_ImgChanType chn, Float32 gamma, Float32 scale)
{
    Int32   m, f, t;
    Float32 p;
    Int16   *table = NULL;

    switch (chn) {
        case FF_ImgChanTypeRed: {
            if (gamma == gammamf_r && scale == scalemf_r) {
                return;
            }
            table = lutmf_r;
            break;
        }
        case FF_ImgChanTypeGreen: {
            if (gamma == gammamf_r && scale == scalemf_r) {
                lmf_g = lutmf_r;
                return;
            }
            if (gamma == gammamf_g && scale == scalemf_g) {
                return;
            }
            table = lutmf_g;
            break;
        }
        case FF_ImgChanTypeBlue: {
            if (gamma == gammamf_r && scale == scalemf_r) {
                lmf_b = lutmf_r;
                return;
            }
            if (gamma == gammamf_g && scale == scalemf_g) {
                lmf_b = lutmf_g;
                return;
            }
            if (gamma == gammamf_b && scale == scalemf_b) {
                return;
            }
            table = lutmf_b;
            break;
        }
        default: {
            UT_ErrFatal ("make_lmf", "invalid channel type");
        }
    }
    for (f = 0; f < 256; ++f) {
        p = scale * pow (f * (1.0 / 255.0), gamma);
        for (m = 0; m < 256; ++m) {
            t = (Int32)((lutmax / 255.0) * m * p + 0.5);
            table[m + (f << 8)] = UT_MAX (0, UT_MIN (t, lutmax));
        }
    }

    switch (chn) {
        case FF_ImgChanTypeRed: {
            gammamf_r = gamma;
            scalemf_r = scale;
            lmf_r = table;
            break;
        }
        case FF_ImgChanTypeGreen: {
            gammamf_g = gamma;
            scalemf_g = scale;
            lmf_g = table;
            break;
        }
        case FF_ImgChanTypeBlue: {
            gammamf_b = gamma;
            scalemf_b = scale;
            lmf_b = table;
            break;
        }
        default: {
            UT_ErrFatal ("make_lmf", "invalid channel type");
        }
    }
    return;
}



/* Store an approximation of function f(x) = pow (x, gamma) in table "lc". */

static void make_lc (Float32 gamma)
{
    Int32 c, t;

    if (gamma == gammac) {
        return;
    }

    for (c = 0; c < lutres; ++c) {
        t = (Int32)(255.0 * pow (c * (1.0 / (Float32)lutmax), gamma) + 0.5);
        lc[c] = UT_MAX (0, UT_MIN (t, 255));
    }
    for (c = lutres; c < 2 * lutres; ++c) {
        lc[c] = 255;
    }
    gammac = gamma;
    return;
}



/***************************************************************************
 *
 *      Functions which do the actual compositing
 *
 ***************************************************************************/

/* Mix brightness channel "chn" of images "fgimg" and "bgimg".  Store the
result in channel "chn" of image "cmimg".  "X1", "y1", "x2" and "y2" define
the lower left and upper right corner of the region to be processed.
"Lf", "lb" and "lc" are the lookup tables used to do the mixing operations. */

static void mix_bright
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg,
         const Int16 lf[],
         const Int16 lb[],
         const UInt8 lc[])
{
    Int32       y, n;
    const UInt8 *fg, *bg;
    UInt8       *cm, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y) {
        fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
        bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
        cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
        stop = cm + n;
        while (cm < stop) {
            *cm++ = lc [lf [*fg++] + lb [*bg++]];
        }
    }
    return;
}



/* Mix transparency channel "chn" of images "fgimg" and "bgimg".  Store the
result in channel "chn" of image "cmimg".  "X1", "y1", "x2" and "y2" define
the lower left and upper right corner of the region to be processed. */

static void mix_trans
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg,
         Float32 mix)
{
    Int32       y, n, s, t;
    const UInt8 *fg, *bg;
    UInt8       *cm, *stop;

    n = x2 - x1 + 1;
    s = (Int32)(mix * 256.0);
    t = 256 - s;
    for (y = y1; y <= y2; ++y) {
        fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
        bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
        cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
        stop = cm + n;
        while (cm < stop) {
            *cm++ = ((UInt32)*fg++ * s + (UInt32)*bg++ * t) >> 8;
        }
    }
    return;
}



/* Composite brightness channel "chn" of images "fgimg" and "bgimg", using
channel "mchn" of image "fgimg" as the matte.  Scale only the background data
according to the matte.  Store the result in channel "chn" of image "cmimg".
"X1", "y1", "x2" and "y2" define the lower left and upper right corner of
the region to be processed. "Lf", "lmb" and "lc" are the lookup tables used
to do the compositing operations. */

static void comp_bright_b
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         FF_ImgChanType mchn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg,
         const Int16 lf[],
         const Int16 lmb[],
         const UInt8 lc[])
{
    Int32       y, n;
    const UInt8 *fg, *bg, *mt;
    UInt8       *cm, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y) {
        fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
        mt = FF_ImgUByteRow (&fgimg->pimg, mchn, y) + x1;
        bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
        cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
        stop = cm + n;
        while (cm < stop) {
            *cm++ = lc [lf [*fg++] + lmb [*mt++ + ((UInt32)*bg++ << 8)]];
        }
    }
    return;
}



/* Composite brightness channel "chn" of images "fgimg" and "bgimg", using
channel "mchn" of image "mtimg" as the matte.  Scale both, the foreground and
the the background data according to the matte.  Store the result in channel
"chn" of image "cmimg".  "X1", "y1", "x2" and "y2" define the lower left and
upper right corner of the region to be processed. "Lmf", "lmb" and "lc" are
the lookup tables used to do the compositing operations. */

static void comp_bright_fb
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         FF_ImgChanType mchn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId mtimg,
         IP_ImageId cmimg,
         const Int16 lmf[],
         const Int16 lmb[],
         const UInt8 lc[])
{
    Int32       y, n;
    const UInt8 *fg, *bg, *mt;
    UInt8       *cm, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y) {
        fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
        mt = FF_ImgUByteRow (&mtimg->pimg, mchn, y) + x1;
        bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
        cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
        stop = cm + n;
        while (cm < stop) {
            *cm++ = lc [lmf [*mt + ((UInt32)*fg++ << 8)] +
                        lmb [*mt + ((UInt32)*bg++ << 8)]];
            ++mt;
        }
    }
    return;
}



/* Composite transparency channel "chn" of images "fgimg" and "bgimg".
Store the result in channel "chn" of image "cmimg".  "X1", "y1", "x2"
and "y2" define the lower left and upper right corner of the region to
be processed.
Note: The expression "(256 - ...) * (255 * ...)" in the compositing loop
below is not a typo; the expression is correct as it is. */

static void comp_trans
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg)
{
    Int32       y, n;
    const UInt8 *fg, *bg;
    UInt8       *cm, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y) {
        fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
        bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
        cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
        stop = cm + n;
        while (cm < stop) {
            *cm++ = 255 - (((256 - (UInt32)*fg++) * (255 - (UInt32)*bg++)) >> 8);
        }
    }
    return;
}



/* Composite channel "chn" of images "fgimg" and "bgimg", using the depth
channels of "fgimg" and "bgimg" as the matte. Store the result in "cmimg".
"X1", "y1", "x2" and "y2" define the lower left and upper right corner of
the region to be processed. */

static void comp_depth
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg)
{
    Int32               y, n;
    const Float32       *fd, *bd;

    n = x2 - x1 + 1;
    if (cmimg->pimg.channel[chn] == FF_ImgFmtTypeUByte) {
        for (y = y1; y <= y2; ++y) {
            const UInt8 *fg, *bg;
            UInt8       *cm, *stop;

            fg = FF_ImgUByteRow (&fgimg->pimg, chn, y) + x1;
            bg = FF_ImgUByteRow (&bgimg->pimg, chn, y) + x1;
            cm = FF_ImgUByteRow (&cmimg->pimg, chn, y) + x1;
            fd = FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeDepth, y) + x1;
            bd = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeDepth, y) + x1;
            stop = cm + n;
            while (cm < stop) {
                if (*fd < *bd) {
                    *cm = *bg;
                } else {
                    *cm = *fg;
                }
                ++fg;
                ++bg;
                ++cm;
                ++fd;
                ++bd;
            }
        }
    } else {
        for (y = y1; y <= y2; ++y) {
            const Float32       *fg, *bg;
            Float32             *cm, *stop;

            fg = FF_ImgFloatRow (&fgimg->pimg, chn, y) + x1;
            bg = FF_ImgFloatRow (&bgimg->pimg, chn, y) + x1;
            cm = FF_ImgFloatRow (&cmimg->pimg, chn, y) + x1;
            fd = FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeDepth, y) + x1;
            bd = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeDepth, y) + x1;
            stop = cm + n;
            while (cm < stop) {
                if (*fd < *bd) {
                    *cm = *bg;
                } else {
                    *cm = *fg;
                }
                ++fg;
                ++bg;
                ++cm;
                ++fd;
                ++bd;
            }
        }
    }
    return;
}



/* Concurrent loop to composite two images using a black and white matte. */

static void conc_comp_bwmatte (void *ptr, Int32 n_conc, Int32 i_conc)
{
    IP_ImageId  fgimg, bgimg, cmimg, mtimg;
    Int32               xmax, height, ymin, ymax;

    fgimg = ((bwmparms *) ptr)->fgimg;
    bgimg = ((bwmparms *) ptr)->bgimg;
    cmimg = ((bwmparms *) ptr)->cmimg;
    mtimg = ((bwmparms *) ptr)->mtimg;
    xmax = ((bwmparms *) ptr)->width - 1;
    height = ((bwmparms *) ptr)->height;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    if (IP_StateDrawMask[FF_ImgChanTypeGray] &&
        fgimg->pimg.channel[FF_ImgChanTypeGray] &&
        bgimg->pimg.channel[FF_ImgChanTypeGray] &&
        cmimg->pimg.channel[FF_ImgChanTypeGray])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGray, FF_ImgChanTypeMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_g, lmb_g, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGray, FF_ImgChanTypeMatte,
                            fgimg, bgimg, cmimg,
                            lf_g, lmb_g, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeRed] &&
        fgimg->pimg.channel[FF_ImgChanTypeRed] &&
        bgimg->pimg.channel[FF_ImgChanTypeRed] &&
        cmimg->pimg.channel[FF_ImgChanTypeRed])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeRed, FF_ImgChanTypeMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_r, lmb_r, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeRed, FF_ImgChanTypeMatte,
                            fgimg, bgimg, cmimg,
                            lf_r, lmb_r, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeGreen] &&
        fgimg->pimg.channel[FF_ImgChanTypeGreen] &&
        bgimg->pimg.channel[FF_ImgChanTypeGreen] &&
        cmimg->pimg.channel[FF_ImgChanTypeGreen])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGreen, FF_ImgChanTypeMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_g, lmb_g, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGreen, FF_ImgChanTypeMatte,
                            fgimg, bgimg, cmimg,
                            lf_g, lmb_g, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeBlue] &&
        fgimg->pimg.channel[FF_ImgChanTypeBlue] &&
        bgimg->pimg.channel[FF_ImgChanTypeBlue] &&
        cmimg->pimg.channel[FF_ImgChanTypeBlue])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeBlue, FF_ImgChanTypeMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_b, lmb_b, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeBlue, FF_ImgChanTypeMatte,
                            fgimg, bgimg, cmimg,
                            lf_b, lmb_b, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeMatte] &&
        bgimg->pimg.channel[FF_ImgChanTypeMatte] &&
        cmimg->pimg.channel[FF_ImgChanTypeMatte])
    {
        comp_trans (0, ymin, xmax, ymax, FF_ImgChanTypeMatte,
                    mtimg? mtimg: fgimg, bgimg, cmimg);
    }
    return;
}



/* Concurrent loop to composite two images using a colored matte. */

static void conc_comp_clrmatte (void *ptr, Int32 n_conc, Int32 i_conc)
{
    IP_ImageId  fgimg, bgimg, cmimg, mtimg;
    Int32               xmax, height, ymin, ymax;

    fgimg = ((clrmparms *) ptr)->fgimg;
    bgimg = ((clrmparms *) ptr)->bgimg;
    cmimg = ((clrmparms *) ptr)->cmimg;
    mtimg = ((clrmparms *) ptr)->mtimg;
    xmax = ((clrmparms *) ptr)->width - 1;
    height = ((clrmparms *) ptr)->height;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    if (IP_StateDrawMask[FF_ImgChanTypeRed] &&
        fgimg->pimg.channel[FF_ImgChanTypeRed] &&
        bgimg->pimg.channel[FF_ImgChanTypeRed] &&
        cmimg->pimg.channel[FF_ImgChanTypeRed])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeRed, FF_ImgChanTypeRedMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_r, lmb_r, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeRed, FF_ImgChanTypeRedMatte,
                            fgimg, bgimg, cmimg,
                            lf_r, lmb_r, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeGreen] &&
        fgimg->pimg.channel[FF_ImgChanTypeGreen] &&
        bgimg->pimg.channel[FF_ImgChanTypeGreen] &&
        cmimg->pimg.channel[FF_ImgChanTypeGreen])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGreen, FF_ImgChanTypeGreenMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_g, lmb_g, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeGreen, FF_ImgChanTypeGreenMatte,
                            fgimg, bgimg, cmimg,
                            lf_g, lmb_g, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeBlue] &&
        fgimg->pimg.channel[FF_ImgChanTypeBlue] &&
        bgimg->pimg.channel[FF_ImgChanTypeBlue] &&
        cmimg->pimg.channel[FF_ImgChanTypeBlue])
    {
        if (mtimg)
        {
            comp_bright_fb (0, ymin, xmax, ymax,
                            FF_ImgChanTypeBlue, FF_ImgChanTypeBlueMatte,
                            fgimg, bgimg, mtimg, cmimg,
                            lmf_b, lmb_b, lc);
        }
        else
        {
            comp_bright_b  (0, ymin, xmax, ymax,
                            FF_ImgChanTypeBlue, FF_ImgChanTypeBlueMatte,
                            fgimg, bgimg, cmimg,
                            lf_b, lmb_b, lc);
        }
    }
    if (IP_StateDrawMask[FF_ImgChanTypeRedMatte] &&
        bgimg->pimg.channel[FF_ImgChanTypeRedMatte] &&
        cmimg->pimg.channel[FF_ImgChanTypeRedMatte])
    {
        comp_trans (0, ymin, xmax, ymax, FF_ImgChanTypeRedMatte,
                    mtimg? mtimg: fgimg, bgimg, cmimg);
    }
    if (IP_StateDrawMask[FF_ImgChanTypeGreenMatte] &&
        bgimg->pimg.channel[FF_ImgChanTypeGreenMatte] &&
        cmimg->pimg.channel[FF_ImgChanTypeGreenMatte])
    {
        comp_trans (0, ymin, xmax, ymax, FF_ImgChanTypeGreenMatte,
                    mtimg? mtimg: fgimg, bgimg, cmimg);
    }
    if (IP_StateDrawMask[FF_ImgChanTypeBlueMatte] &&
        bgimg->pimg.channel[FF_ImgChanTypeBlueMatte] &&
        cmimg->pimg.channel[FF_ImgChanTypeBlueMatte])
    {
        comp_trans (0, ymin, xmax, ymax, FF_ImgChanTypeBlueMatte,
                    mtimg? mtimg: fgimg, bgimg, cmimg);
    }
    return;
}



/* Concurrent loop to composite two images using depth mattes. */

static void conc_comp_zmatte (void *ptr, Int32 n_conc, Int32 i_conc)
{
    IP_ImageId  fgimg, bgimg, cmimg;
    Int32               xmax, height, ymin, ymax, chn;

    fgimg = ((zmparms *) ptr)->fgimg;
    bgimg = ((zmparms *) ptr)->bgimg;
    cmimg = ((zmparms *) ptr)->cmimg;
    xmax = ((zmparms *) ptr)->width - 1;
    height = ((zmparms *) ptr)->height;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    for (chn = FF_ImgChanTypeGray; chn < FF_NumImgChanTypes; ++chn)
    {
        if ((chn == FF_ImgChanTypeDepth) ||
            !IP_StateDrawMask[chn] ||
            !fgimg->pimg.channel[chn] ||
            !bgimg->pimg.channel[chn] ||
            !cmimg->pimg.channel[chn])
            continue;
        comp_depth (0, ymin, xmax, ymax, chn, fgimg, bgimg, cmimg);
    }
    if (IP_StateDrawMask[FF_ImgChanTypeDepth] && cmimg->pimg.channel[FF_ImgChanTypeDepth])
        comp_depth (0, ymin, xmax, ymax, FF_ImgChanTypeDepth, fgimg, bgimg, cmimg);

    return;
}



/* Concurrent loop to cross-dissolve between two images. */

static void conc_crossimage (void *ptr, Int32 n_conc, Int32 i_conc)
{
    IP_ImageId  fgimg, bgimg, cmimg;
    Int32               xmax, height, ymin, ymax, chn;
    Float32     mix;

    fgimg = ((crossparms *) ptr)->fgimg;
    bgimg = ((crossparms *) ptr)->bgimg;
    cmimg = ((crossparms *) ptr)->cmimg;
    xmax = ((crossparms *) ptr)->width - 1;
    height = ((crossparms *) ptr)->height;
    mix = ((crossparms *) ptr)->mix;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    for (chn = FF_ImgChanTypeGray; chn <= FF_ImgChanTypeBlue; ++chn)
    {
        if (!IP_StateDrawMask[chn] ||
            !fgimg->pimg.channel[chn] ||
            !bgimg->pimg.channel[chn] ||
            !cmimg->pimg.channel[chn])
            continue;
        mix_bright (0, ymin, xmax, ymax, chn,
                    fgimg, bgimg, cmimg,
                    lf_r, lb_r, lc);
    }
    for (chn = FF_ImgChanTypeMatte; chn <= FF_ImgChanTypeBlueMatte; ++chn)
    {
        if (!IP_StateDrawMask[chn] ||
            !fgimg->pimg.channel[chn] ||
            !bgimg->pimg.channel[chn] ||
            !cmimg->pimg.channel[chn])
            continue;
        mix_trans (0, ymin, xmax, ymax, chn, fgimg, bgimg, cmimg, mix);
    }
    return;
}



/***************************************************************************
 *[@e
 *      Name:           IP_CompFastAlpha
 *
 *      Usage:          Composite two images, using a black and white matte,
 *                      fast version.
 *
 *      Synopsis:       UT_Bool IP_CompFastAlpha
 *                              (IP_ImageId fgimg,
 *                               IP_ImageId bgimg,
 *                               IP_ImageId cmimg,
 *                               Float32 f_r, Float32 f_g, Float32 f_b,
 *                               Float32 b_r, Float32 b_g, Float32 b_b,
 *                               IP_ImageId mtimg)
 *
 *      Description:    See the comments on function IP_CompositeAlpha for
 *                      an explanation of the parameters.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       IP_CompositeAlpha
 *
 ***************************************************************************/

UT_Bool IP_CompFastAlpha
        (IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg,
         Float32 f_r, Float32 f_g, Float32 f_b,
         Float32 b_r, Float32 b_g, Float32 b_b,
         IP_ImageId mtimg)
{
    bwmparms    parms;

    /* Initialize the gamma tables. */

    if (cmimg->pimg.desc.gamma < MINGAMMA)
    {
        UT_ErrSetNum (UT_ErrParamRange, str_range_inclusive,
                     str_gamma, MINGAMMA, MAXGAMMA);
        return UT_False;
    }
    if (mtimg)
    {
        make_lmf (FF_ImgChanTypeRed,   fgimg->pimg.desc.gamma, f_r);
        make_lmf (FF_ImgChanTypeGreen, fgimg->pimg.desc.gamma, f_g);
        make_lmf (FF_ImgChanTypeBlue,  fgimg->pimg.desc.gamma, f_b);
    }
    else
    {
        make_lf  (FF_ImgChanTypeRed,   fgimg->pimg.desc.gamma, f_r);
        make_lf  (FF_ImgChanTypeGreen, fgimg->pimg.desc.gamma, f_g);
        make_lf  (FF_ImgChanTypeBlue,  fgimg->pimg.desc.gamma, f_b);
    }
    make_lmb (FF_ImgChanTypeRed,   bgimg->pimg.desc.gamma, b_r);
    make_lmb (FF_ImgChanTypeGreen, bgimg->pimg.desc.gamma, b_g);
    make_lmb (FF_ImgChanTypeBlue,  bgimg->pimg.desc.gamma, b_b);
    make_lc  (1.0 / cmimg->pimg.desc.gamma);

    /* Determine the size of the area in which compositing takes place. */

    parms.width = fgimg->pimg.desc.width;
    parms.width = UT_MIN (parms.width, bgimg->pimg.desc.width);
    parms.width = UT_MIN (parms.width, cmimg->pimg.desc.width);
    if (mtimg)
        parms.width = UT_MIN (parms.width, mtimg->pimg.desc.width);
    parms.height = fgimg->pimg.desc.height;
    parms.height = UT_MIN (parms.height, bgimg->pimg.desc.height);
    parms.height = UT_MIN (parms.height, cmimg->pimg.desc.height);
    if (mtimg)
        parms.height = UT_MIN (parms.height, mtimg->pimg.desc.height);

    /* Do the compositing. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mtimg = mtimg;

    return UT_THREAD_EXEC (conc_comp_bwmatte,
                                &parms, IP_StateNumThreads, stacksize);
}

/***************************************************************************
 *[@e
 *      Name:           IP_CompFastColorMatte
 *
 *      Usage:          Composite two images, using a colored matte,
 *                      fast version.
 *
 *      Synopsis:       UT_Bool IP_CompFastColorMatte
 *                              (IP_ImageId fgimg,
 *                               IP_ImageId bgimg,
 *                               IP_ImageId cmimg,
 *                               Float32 f_r, Float32 f_g, Float32 f_b,
 *                               Float32 b_r, Float32 b_g, Float32 b_b,
 *                               IP_ImageId mtimg)
 *
 *      Description:    See the comments on function IP_CompositeColorMatte
 *                      for an explanation of the parameters.
 *
 *      Return value:   None.
 *
 *      See also:       IP_CompositeColorMatte
 *
 ***************************************************************************/

UT_Bool IP_CompFastColorMatte
        (IP_ImageId fgimg,
         IP_ImageId bgimg,
         IP_ImageId cmimg,
         Float32 f_r, Float32 f_g, Float32 f_b,
         Float32 b_r, Float32 b_g, Float32 b_b,
         IP_ImageId mtimg)
{
    clrmparms   parms;

    /* Initialize the gamma tables. */

    if (cmimg->pimg.desc.gamma < MINGAMMA)
    {
        UT_ErrSetNum (UT_ErrParamRange, str_range_inclusive,
                     str_gamma, MINGAMMA, MAXGAMMA);
        return UT_False;
    }
    if (mtimg)
    {
        make_lmf (FF_ImgChanTypeRed,   fgimg->pimg.desc.gamma, f_r);
        make_lmf (FF_ImgChanTypeGreen, fgimg->pimg.desc.gamma, f_g);
        make_lmf (FF_ImgChanTypeBlue,  fgimg->pimg.desc.gamma, f_b);
    }
    else
    {
        make_lf  (FF_ImgChanTypeRed,   fgimg->pimg.desc.gamma, f_r);
        make_lf  (FF_ImgChanTypeGreen, fgimg->pimg.desc.gamma, f_g);
        make_lf  (FF_ImgChanTypeBlue,  fgimg->pimg.desc.gamma, f_b);
    }
    make_lmb (FF_ImgChanTypeRed,   bgimg->pimg.desc.gamma, b_r);
    make_lmb (FF_ImgChanTypeGreen, bgimg->pimg.desc.gamma, b_g);
    make_lmb (FF_ImgChanTypeBlue,  bgimg->pimg.desc.gamma, b_b);
    make_lc  (1.0 / cmimg->pimg.desc.gamma);

    /* Determine the size of the area in which compositing takes place. */

    parms.width = fgimg->pimg.desc.width;
    parms.width = UT_MIN (parms.width, bgimg->pimg.desc.width);
    parms.width = UT_MIN (parms.width, cmimg->pimg.desc.width);
    if (mtimg)
        parms.width = UT_MIN (parms.width, mtimg->pimg.desc.width);
    parms.height = fgimg->pimg.desc.height;
    parms.height = UT_MIN (parms.height, bgimg->pimg.desc.height);
    parms.height = UT_MIN (parms.height, cmimg->pimg.desc.height);
    if (mtimg)
        parms.height = UT_MIN (parms.height, mtimg->pimg.desc.height);

    /* Do the compositing. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mtimg = mtimg;

    return UT_THREAD_EXEC (conc_comp_clrmatte,
                                &parms, IP_StateNumThreads, stacksize);
}

/***************************************************************************
 *[@e
 *      Name:           IP_CompFastDepth
 *
 *      Usage:          Composite two images, using depth mattes,
 *                      fast version.
 *
 *      Synopsis:       UT_Bool IP_CompFastDepth
 *                              (IP_ImageId fgimg,
 *                               IP_ImageId bgimg,
 *                               IP_ImageId cmimg)
 *
 *      Description:    See the comments on function IP_CompositeDepth
 *                      for an explanation of the parameters.
 *
 *      Return value:   None.
 *
 *      See also:       IP_CompositeDepth
 *
 ***************************************************************************/

UT_Bool IP_CompFastDepth (IP_ImageId fgimg, IP_ImageId bgimg, IP_ImageId cmimg)
{
    zmparms     parms;

    /* Determine the size of the area in which compositing takes place. */

    parms.width = fgimg->pimg.desc.width;
    parms.width = UT_MIN (parms.width, bgimg->pimg.desc.width);
    parms.width = UT_MIN (parms.width, cmimg->pimg.desc.width);
    parms.height = fgimg->pimg.desc.height;
    parms.height = UT_MIN (parms.height, bgimg->pimg.desc.height);
    parms.height = UT_MIN (parms.height, cmimg->pimg.desc.height);

    /* Do the compositing. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;

    return UT_THREAD_EXEC (conc_comp_zmatte,
                                &parms, IP_StateNumThreads, stacksize);
}

/***************************************************************************
 *[@e
 *      Name:           IP_CrossDissolveFast
 *
 *      Usage:          Crosss-dissolve between two images, fast version.
 *
 *      Synopsis:       UT_Bool IP_CrossDissolveFast
 *                              (IP_ImageId fgimg,
 *                               IP_ImageId bgimg,
 *                               IP_ImageId cmimg,
 *                               Float32 mix)
 *
 *      Description:    See the comments on function IP_CrossDissolve for an
 *                      explanation of the parameters.
 *
 *      Return value:   None.
 *
 *      See also:       IP_CrossDissolve
 *
 ***************************************************************************/

UT_Bool IP_CrossDissolveFast
        (IP_ImageId fgimg, IP_ImageId bgimg, IP_ImageId cmimg, Float32 mix)
{
    crossparms  parms;

    /* Initialize the gamma-correction tables. */

    if (cmimg->pimg.desc.gamma < MINGAMMA)
    {
        UT_ErrSetNum (UT_ErrParamRange, str_range_inclusive,
                     str_gamma, MINGAMMA, MAXGAMMA);
        return UT_False;
    }
    make_lf (FF_ImgChanTypeRed, fgimg->pimg.desc.gamma, mix);
    make_lb (FF_ImgChanTypeRed, bgimg->pimg.desc.gamma, 1.0 - mix);
    make_lc (1.0 / cmimg->pimg.desc.gamma);

    /* Determine the size of the area to be processed. */

    parms.width = fgimg->pimg.desc.width;
    parms.width = UT_MIN (parms.width, bgimg->pimg.desc.width);
    parms.width = UT_MIN (parms.width, cmimg->pimg.desc.width);
    parms.height = fgimg->pimg.desc.height;
    parms.height = UT_MIN (parms.height, bgimg->pimg.desc.height);
    parms.height = UT_MIN (parms.height, cmimg->pimg.desc.height);

    /* Do the cross-dissolve. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mix = mix;

    return UT_THREAD_EXEC (conc_crossimage,
                                &parms, IP_StateNumThreads, stacksize);
}
