/**************************************************************************
 *{@C
 *      Copyright:      1988-2024 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_CompSlow.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Image compositing functions, slower all-purpose version.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_CompSlowAlpha
 *                      IP_CompSlowColorMatte
 *                      IP_CompSlowDepth
 *                      IP_CrossDissolveSlow
 *
 **************************************************************************/

#include <stdio.h>

#include "UT_Compat.h"
#include "UT_Macros.h"
#include "UT_Error.h"
#include "UT_Memory.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;
        Float32         f_r,            /* Foreground scaling factors */
                        f_g,
                        f_b,
                        b_r,            /* Background scaling factors */
                        b_g,
                        b_b,
                        *ub_fg_lin,     /* Gamma correction tables */
                        *ub_bg_lin,
                        *f_fg_lin,
                        *f_bg_lin,
                        *f_lin_cm,
                        *front,         /* Temporary data buffers */
                        *back,
                        *comp,
                        *trans;
        } 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;
        Float32         f_r,            /* Foreground scaling factors */
                        f_g,
                        f_b,
                        b_r,            /* Background scaling factors */
                        b_g,
                        b_b,
                        *ub_fg_lin,     /* Gamma correction tables */
                        *ub_bg_lin,
                        *f_fg_lin,
                        *f_bg_lin,
                        *f_lin_cm,
                        *front,         /* Temporary data buffers */
                        *back,
                        *comp,
                        *r_trans,
                        *g_trans,
                        *b_trans;
        } 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;
        Float32         *fg_data,       /* Temporary data buffers */
                        *bg_data,
                        *cm_data,
                        *fg_depth,
                        *bg_depth;
        } 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,            /* Mixing factor */
                        *ub_fg_lin,     /* Gamma correction tables */
                        *ub_bg_lin,
                        *f_fg_lin,
                        *f_bg_lin,
                        *f_lin_cm,
                        *fg_data,       /* Temporary data buffers */
                        *bg_data,
                        *cm_data;
        } crossparms;



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

#define stacksize       (1 << 12)



/***************************************************************************
 *
 *      Static image processing functions
 *
 ***************************************************************************/

/* Composite the foreground and background data, "front" and "back", of a
color or brightness channel, scaling the background data according to the
foreground transparency channel data, "trans". Store the result in "comp". */

static void comp_bright_b
        (Int32 n,
         const Float32 front[],
         const Float32 back[],
         const Float32 trans[],
         Float32 fgscale,
         Float32 bgscale,
         Float32 comp[])
{
    const Float32       *fgsrc, *bgsrc, *trsrc;
    Float32     *cdest, *cstop;

    fgsrc = front;
    bgsrc = back;
    trsrc = trans;
    cdest = comp;
    cstop = comp + n;
    while (cdest < cstop)
        *(cdest++) =
            fgscale * *(fgsrc++) +
            bgscale * *(bgsrc++) * (1.0 - *(trsrc++));
    return;
}



/* Composite the foreground and background data, "front" and "back", of a
color or brightness channel, scaling the foreground and the background data
according to the foreground transparency channel data, "trans". Store the
result in "comp". */

static void comp_bright_fb
        (Int32 n,
         const Float32 front[],
         const Float32 back[],
         const Float32 trans[],
         Float32 fgscale,
         Float32 bgscale,
         Float32 comp[])
{
    const Float32       *fgsrc, *bgsrc, *trsrc;
    Float32     *cdest, *cstop, tmp;

    fgsrc = front;
    bgsrc = back;
    trsrc = trans;
    cdest = comp;
    cstop = comp + n;
    while (cdest < cstop)
    {
        tmp = *(trsrc++);
        *(cdest++) =
            fgscale * *(fgsrc++) * tmp +
            bgscale * *(bgsrc++) * (1.0 - tmp);
    }
    return;
}



/* Composite the foreground and background data, "front" and "back", of a
transparency channel. Store the result in "comp". */

static void comp_trans
        (Int32 n,
         const Float32 front[],
         const Float32 back[],
         Float32 comp[])
{
    const Float32       *fgsrc, *bgsrc;
    Float32     *cdest, *cstop;

    fgsrc = front;
    bgsrc = back;
    cdest = comp;
    cstop = comp + n;
    while (cdest < cstop)
        *(cdest++) = 1.0 - (1.0 - *(fgsrc++)) * (1.0 - *(bgsrc++));
    return;
}



/* Composite the foreground and background pixel data for some channel,
"front" and "back", according to the foreground and background distance,
"fgdist" and "bgdist". Store the result in "comp". */

static void comp_depth
        (Int32 n,
         const Float32 front[],
         const Float32 back[],
         const Float32 fgdist[],
         const Float32 bgdist[],
         Float32 comp[])
{
    const Float32       *fgsrc, *bgsrc, *fgdsrc, *bgdsrc;
    Float32     *cdest, *cstop;

    fgsrc = front;
    bgsrc = back;
    fgdsrc = fgdist;
    bgdsrc = bgdist;
    cdest = comp;
    cstop = comp + n;
    while (cdest < cstop)
    {
        if (*fgdsrc < *bgdsrc)
            *cdest = *bgsrc;
        else
            *cdest = *fgsrc;
        ++fgsrc;
        ++bgsrc;
        ++fgdsrc;
        ++bgdsrc;
        ++cdest;
    }
    return;
}



/* Perform a cross-dissolve on the pixel data for some channel. */

static void crosspixels
        (Int32 n,
         Float32 s,
         const Float32 front[],
         const Float32 back[],
         Float32 cross[])
{
    const Float32       *fgsrc, *bgsrc;
    Float32     *cdest, *cstop, t;

    t = 1.0 - s;
    fgsrc = front;
    bgsrc = back;
    cdest = cross;
    cstop = cross + n;
    while (cdest < cstop)
        *(cdest++) = *(fgsrc++) * s + *(bgsrc++) * t;
    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       width, height, ymin, ymax, y;
    Float32     f_r, f_g, f_b, b_r, b_g, b_b,
                *ub_fg_lin, *ub_bg_lin, *f_fg_lin, *f_bg_lin, *f_lin_cm,
                *front, *back, *comp, *trans;
    Float32     *bptr = NULL, *tptr = NULL;

    /* Initialization */

    fgimg = ((bwmparms *) ptr)->fgimg;
    bgimg = ((bwmparms *) ptr)->bgimg;
    cmimg = ((bwmparms *) ptr)->cmimg;
    mtimg = ((bwmparms *) ptr)->mtimg;
    width = ((bwmparms *) ptr)->width;
    height = ((bwmparms *) ptr)->height;
    f_r = ((bwmparms *) ptr)->f_r;
    f_g = ((bwmparms *) ptr)->f_g;
    f_b = ((bwmparms *) ptr)->f_b;
    b_r = ((bwmparms *) ptr)->b_r;
    b_g = ((bwmparms *) ptr)->b_g;
    b_b = ((bwmparms *) ptr)->b_b;
    ub_fg_lin = ((bwmparms *) ptr)->ub_fg_lin;
    ub_bg_lin = ((bwmparms *) ptr)->ub_bg_lin;
    f_fg_lin = ((bwmparms *) ptr)->f_fg_lin;
    f_bg_lin = ((bwmparms *) ptr)->f_bg_lin;
    f_lin_cm = ((bwmparms *) ptr)->f_lin_cm;
    front = ((bwmparms *) ptr)->front + i_conc * width;
    back = ((bwmparms *) ptr)->back + i_conc * width;
    comp = ((bwmparms *) ptr)->comp + i_conc * width;
    trans = ((bwmparms *) ptr)->trans + i_conc * width;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    /* Main loop */

    for (y = ymin; y < ymax; ++y) {
        if (mtimg) {
            switch (mtimg->pimg.channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&mtimg->pimg, FF_ImgChanTypeMatte, y),
                         trans);
                    tptr = trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    tptr = FF_ImgFloatRow (&mtimg->pimg, FF_ImgChanTypeMatte, y);
                    break;
                }
            }
        } else {
            switch (fgimg->pimg.channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeMatte, y),
                         trans);
                    tptr = trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    tptr = FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeMatte, y);
                    break;
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeGray] &&
            fgimg->pimg.channel[FF_ImgChanTypeGray] &&
            bgimg->pimg.channel[FF_ImgChanTypeGray] &&
            cmimg->pimg.channel[FF_ImgChanTypeGray]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeGray]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeGray, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeGray, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeGray]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeGray, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeGray, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, tptr, f_g, b_g, comp);
            } else {
                comp_bright_b (width, front, back, tptr, f_g, b_g, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeGray]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeGray, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeGray, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeRed] &&
            fgimg->pimg.channel[FF_ImgChanTypeRed] &&
            bgimg->pimg.channel[FF_ImgChanTypeRed] &&
            cmimg->pimg.channel[FF_ImgChanTypeRed]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeRed, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeRed, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeRed, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeRed, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, tptr, f_r, b_r, comp);
            } else {
                comp_bright_b (width, front, back, tptr, f_r, b_r, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeRed, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeRed, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeGreen] &&
            fgimg->pimg.channel[FF_ImgChanTypeGreen] &&
            bgimg->pimg.channel[FF_ImgChanTypeGreen] &&
            cmimg->pimg.channel[FF_ImgChanTypeGreen]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeGreen, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeGreen, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeGreen, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeGreen, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, tptr, f_g, b_g, comp);
            } else {
                comp_bright_b (width, front, back, tptr, f_g, b_g, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeGreen, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeGreen, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeBlue] &&
            fgimg->pimg.channel[FF_ImgChanTypeBlue] &&
            bgimg->pimg.channel[FF_ImgChanTypeBlue] &&
            cmimg->pimg.channel[FF_ImgChanTypeBlue]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeBlue, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeBlue, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeBlue, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeBlue, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, tptr, f_b, b_b, comp);
            } else {
                comp_bright_b (width, front, back, tptr, f_b, b_b, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeBlue, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeBlue, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeMatte] &&
            bgimg->pimg.channel[FF_ImgChanTypeMatte] &&
            cmimg->pimg.channel[FF_ImgChanTypeMatte]) {
            switch (bgimg->pimg.channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeMatte, y),
                         back);
                    bptr = back;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bptr = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeUByte: {
                    comp_trans (width, tptr, bptr, comp);
                    IP_CopyFloat2UByte
                        (width,
                         comp,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeMatte, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    comp_trans
                        (width,
                         tptr,
                         bptr,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeMatte, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_bwmatte", "invalid channel type");
                }
            }
        }
    }
    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               width, height, ymin, ymax, y;
    Float32     f_r, f_g, f_b, b_r, b_g, b_b,
                *ub_fg_lin, *ub_bg_lin, *f_fg_lin, *f_bg_lin, *f_lin_cm,
                *front, *back, *comp,
                *r_trans, *g_trans, *b_trans;
    Float32     *bptr = NULL, *r_tptr = NULL, *g_tptr = NULL, *b_tptr = NULL;

    /* Initialization */

    fgimg = ((clrmparms *) ptr)->fgimg;
    bgimg = ((clrmparms *) ptr)->bgimg;
    cmimg = ((clrmparms *) ptr)->cmimg;
    mtimg = ((clrmparms *) ptr)->mtimg;
    width = ((clrmparms *) ptr)->width;
    height = ((clrmparms *) ptr)->height;
    f_r = ((clrmparms *) ptr)->f_r;
    f_g = ((clrmparms *) ptr)->f_g;
    f_b = ((clrmparms *) ptr)->f_b;
    b_r = ((clrmparms *) ptr)->b_r;
    b_g = ((clrmparms *) ptr)->b_g;
    b_b = ((clrmparms *) ptr)->b_b;
    ub_fg_lin = ((clrmparms *) ptr)->ub_fg_lin;
    ub_bg_lin = ((clrmparms *) ptr)->ub_bg_lin;
    f_fg_lin = ((clrmparms *) ptr)->f_fg_lin;
    f_bg_lin = ((clrmparms *) ptr)->f_bg_lin;
    f_lin_cm = ((clrmparms *) ptr)->f_lin_cm;
    front = ((clrmparms *) ptr)->front + i_conc * width;
    back = ((clrmparms *) ptr)->back + i_conc * width;
    comp = ((clrmparms *) ptr)->comp + i_conc * width;
    r_trans = ((clrmparms *) ptr)->r_trans + i_conc * width;
    g_trans = ((clrmparms *) ptr)->g_trans + i_conc * width;
    b_trans = ((clrmparms *) ptr)->b_trans + i_conc * width;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    /* Main loop */

    for (y = ymin; y < ymax; ++y) {
        if (mtimg) {
            switch (mtimg->pimg.channel[FF_ImgChanTypeRedMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&mtimg->pimg, FF_ImgChanTypeRedMatte, y),
                         r_trans);
                    r_tptr = r_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    r_tptr = FF_ImgFloatRow (&mtimg->pimg, FF_ImgChanTypeRedMatte, y);
                    break;
                }
            }
            switch (mtimg->pimg.channel[FF_ImgChanTypeGreenMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&mtimg->pimg, FF_ImgChanTypeGreenMatte, y),
                         g_trans);
                    g_tptr = g_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    g_tptr = FF_ImgFloatRow (&mtimg->pimg,
                                             FF_ImgChanTypeGreenMatte, y);
                    break;
                }
            }
            switch (mtimg->pimg.channel[FF_ImgChanTypeBlueMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&mtimg->pimg, FF_ImgChanTypeBlueMatte, y),
                         b_trans);
                    b_tptr = b_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    b_tptr = FF_ImgFloatRow (&mtimg->pimg,
                                                FF_ImgChanTypeBlueMatte, y);
                    break;
                }
            }
        } else {
            switch (fgimg->pimg.channel[FF_ImgChanTypeRedMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeRedMatte, y),
                         r_trans);
                    r_tptr = r_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    r_tptr = FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeRedMatte, y);
                    break;
                }
            }
            switch (fgimg->pimg.channel[FF_ImgChanTypeGreenMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeGreenMatte, y),
                         g_trans);
                    g_tptr = g_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    g_tptr = FF_ImgFloatRow (&fgimg->pimg,
                                             FF_ImgChanTypeGreenMatte, y);
                    break;
                }
            }
            switch (fgimg->pimg.channel[FF_ImgChanTypeBlueMatte]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeBlueMatte, y),
                         b_trans);
                    b_tptr = b_trans;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    b_tptr = FF_ImgFloatRow (&fgimg->pimg,
                                                FF_ImgChanTypeBlueMatte, y);
                    break;
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeRed] &&
            fgimg->pimg.channel[FF_ImgChanTypeRed] &&
            bgimg->pimg.channel[FF_ImgChanTypeRed] &&
            cmimg->pimg.channel[FF_ImgChanTypeRed]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeRed, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeRed, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeRed, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeRed, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, r_tptr, f_r, b_r, comp);
            } else {
                comp_bright_b (width, front, back, r_tptr, f_r, b_r, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeRed]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeRed, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeRed, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeGreen] &&
            fgimg->pimg.channel[FF_ImgChanTypeGreen] &&
            bgimg->pimg.channel[FF_ImgChanTypeGreen] &&
            cmimg->pimg.channel[FF_ImgChanTypeGreen]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeGreen, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeGreen, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeGreen, y),
                         ub_bg_lin,
                         back);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeGreen, y),
                         f_bg_lin,
                         back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, g_tptr, f_g, b_g, comp);
            } else {
                comp_bright_b (width, front, back, g_tptr, f_g, b_g, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeGreen]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeGreen, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm, FF_ImgFloatRow (&cmimg->pimg,
                                            FF_ImgChanTypeGreen, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeBlue] &&
            fgimg->pimg.channel[FF_ImgChanTypeBlue] &&
            bgimg->pimg.channel[FF_ImgChanTypeBlue] &&
            cmimg->pimg.channel[FF_ImgChanTypeBlue]) {
            switch (fgimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeBlue, y),
                         ub_fg_lin,
                         front);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg,
                                            FF_ImgChanTypeBlue, y),
                         f_fg_lin,
                         front);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeBlue, y),
                         ub_bg_lin,
                         back);
                        break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeBlue, y),
                         f_bg_lin,
                     back);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            if (mtimg) {
                comp_bright_fb (width, front, back, b_tptr, f_b, b_b, comp);
            } else {
                comp_bright_b (width, front, back, b_tptr, f_b, b_b, comp);
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeBlue]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeBlue, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         comp,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeBlue, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeRedMatte] &&
            bgimg->pimg.channel[FF_ImgChanTypeRedMatte] &&
            cmimg->pimg.channel[FF_ImgChanTypeRedMatte]) {
            switch (bgimg->pimg.channel[FF_ImgChanTypeRedMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeRedMatte, y),
                         back);
                    bptr = back;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bptr = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeRedMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeRedMatte]) {
                case FF_ImgFmtTypeUByte: {
                    comp_trans (width, r_tptr, bptr, comp);
                    IP_CopyFloat2UByte
                        (width,
                         comp,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeRedMatte, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    comp_trans
                        (width,
                         r_tptr,
                         bptr,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeRedMatte, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeGreenMatte] &&
            bgimg->pimg.channel[FF_ImgChanTypeGreenMatte] &&
            cmimg->pimg.channel[FF_ImgChanTypeGreenMatte]) {
            switch (bgimg->pimg.channel[FF_ImgChanTypeGreenMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeGreenMatte, y),
                         back);
                    bptr = back;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bptr = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeGreenMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeGreenMatte]) {
                case FF_ImgFmtTypeUByte: {
                    comp_trans (width, g_tptr, bptr, comp);
                    IP_CopyFloat2UByte
                        (width,
                         comp,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeGreenMatte, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    comp_trans
                        (width,
                         g_tptr,
                         bptr,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeGreenMatte, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
        if (IP_StateDrawMask[FF_ImgChanTypeBlueMatte] &&
            bgimg->pimg.channel[FF_ImgChanTypeBlueMatte] &&
            cmimg->pimg.channel[FF_ImgChanTypeBlueMatte]) {
            switch (bgimg->pimg.channel[FF_ImgChanTypeBlueMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeBlueMatte, y),
                         back);
                    bptr = back;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bptr = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeBlueMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[FF_ImgChanTypeBlueMatte]) {
                case FF_ImgFmtTypeUByte: {
                    comp_trans (width, b_tptr, bptr, comp);
                    IP_CopyFloat2UByte
                        (width,
                         comp,
                         FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeBlueMatte, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    comp_trans
                        (width,
                         b_tptr,
                         bptr,
                         FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeBlueMatte, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_clrmatte", "invalid channel type");
                }
            }
        }
    }
    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               width, height, ymin, ymax, y, chn;
    Float32     *fg_data, *bg_data, *cm_data, *fg_depth, *bg_depth;
    Float32     *fg_data_ptr = NULL, *bg_data_ptr = NULL,
                *fg_depth_ptr = NULL, *bg_depth_ptr = NULL;

    /* Intialization */

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

    /* Main loop */

    for (y = ymin; y < ymax; ++y) {
        switch (fgimg->pimg.channel[FF_ImgChanTypeDepth]) {
            case FF_ImgFmtTypeNone: {
                break;
            }
            case FF_ImgFmtTypeUByte: {
                IP_CopyUByte2Float
                    (width,
                     FF_ImgUByteRow (&fgimg->pimg, FF_ImgChanTypeDepth, y),
                     fg_depth);
                fg_depth_ptr = fg_depth;
                break;
            }
            case FF_ImgFmtTypeFloat: {
                fg_depth_ptr = FF_ImgFloatRow (&fgimg->pimg, FF_ImgChanTypeDepth, y);
                break;
            }
        }
        switch (bgimg->pimg.channel[FF_ImgChanTypeDepth]) {
            case FF_ImgFmtTypeNone: {
                break;
            }
            case FF_ImgFmtTypeUByte: {
                IP_CopyUByte2Float
                    (width,
                     FF_ImgUByteRow (&bgimg->pimg, FF_ImgChanTypeDepth, y),
                     bg_depth);
                bg_depth_ptr = bg_depth;
                break;
            }
            case FF_ImgFmtTypeFloat: {
                bg_depth_ptr = FF_ImgFloatRow (&bgimg->pimg, FF_ImgChanTypeDepth, y);
                break;
            }
        }
        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;
            }
            switch (fgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, chn, y),
                         fg_data);
                    fg_data_ptr = fg_data;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    fg_data_ptr = FF_ImgFloatRow (&fgimg->pimg, chn, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_zmatte", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, chn, y),
                         bg_data);
                    bg_data_ptr = bg_data;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bg_data_ptr = FF_ImgFloatRow (&bgimg->pimg, chn, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_zmatte", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    comp_depth
                        (width,
                         fg_data_ptr, bg_data_ptr,
                         fg_depth_ptr, bg_depth_ptr,
                         cm_data);
                    IP_CopyFloat2UByte
                        (width, cm_data,
                         FF_ImgUByteRow (&cmimg->pimg, chn, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    comp_depth
                        (width,
                         fg_data_ptr, bg_data_ptr,
                         fg_depth_ptr, bg_depth_ptr,
                         FF_ImgFloatRow (&cmimg->pimg, chn, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_comp_zmatte", "invalid channel type");
                }
            }
        }
        switch (cmimg->pimg.channel[FF_ImgChanTypeDepth]) {
            case FF_ImgFmtTypeUByte: {
                comp_depth
                    (width,
                     fg_depth_ptr, bg_depth_ptr,
                     fg_depth_ptr, bg_depth_ptr,
                     cm_data);
                IP_CopyFloat2UByte
                    (width, cm_data,
                     FF_ImgUByteRow (&cmimg->pimg, FF_ImgChanTypeDepth, y));
                break;
            }
            case FF_ImgFmtTypeFloat: {
                comp_depth
                    (width,
                     fg_depth_ptr, bg_depth_ptr,
                     fg_depth_ptr, bg_depth_ptr,
                     FF_ImgFloatRow (&cmimg->pimg, FF_ImgChanTypeDepth, y));
                break;
            }
            default: {
                UT_ErrFatal ("conc_comp_zmatte", "invalid channel type");
            }
        }
    }
    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               width, height, ymin, ymax, y, chn;
    Float32     mix,
                *ub_fg_lin, *ub_bg_lin, *f_fg_lin, *f_bg_lin, *f_lin_cm,
                *fg_data, *bg_data, *cm_data;
    Float32     *fg_data_ptr = NULL, *bg_data_ptr = NULL, *cm_data_ptr = NULL;

    /* Initialization */

    fgimg = ((crossparms *) ptr)->fgimg;
    bgimg = ((crossparms *) ptr)->bgimg;
    cmimg = ((crossparms *) ptr)->cmimg;
    width = ((crossparms *) ptr)->width;
    height = ((crossparms *) ptr)->height;
    mix = ((crossparms *) ptr)->mix;
    ub_fg_lin = ((crossparms *) ptr)->ub_fg_lin;
    ub_bg_lin = ((crossparms *) ptr)->ub_bg_lin;
    f_fg_lin = ((crossparms *) ptr)->f_fg_lin;
    f_bg_lin = ((crossparms *) ptr)->f_bg_lin;
    f_lin_cm = ((crossparms *) ptr)->f_lin_cm;
    fg_data = ((crossparms *) ptr)->fg_data + i_conc * width;
    bg_data = ((crossparms *) ptr)->bg_data + i_conc * width;
    cm_data = ((crossparms *) ptr)->cm_data + i_conc * width;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc -1;

    /* Main loop */

    for (y = ymin; y < ymax; ++y) {
        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;
            }
            switch (fgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, chn, y),
                         ub_fg_lin,
                         fg_data);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&fgimg->pimg, chn, y),
                         f_fg_lin,
                         fg_data);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, chn, y),
                         ub_bg_lin,
                         bg_data);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         FF_ImgFloatRow (&bgimg->pimg, chn, y),
                         f_bg_lin,
                         bg_data);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
            crosspixels (width, mix, fg_data, bg_data, cm_data);
            switch (cmimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_GammaFloat2UByte
                        (width,
                         cm_data,
                         f_lin_cm,
                         FF_ImgUByteRow (&cmimg->pimg, chn, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    IP_GammaFloat2Float
                        (width,
                         cm_data,
                         f_lin_cm,
                         FF_ImgFloatRow (&cmimg->pimg, chn, y));
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
        }
        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;
            }
            switch (fgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&fgimg->pimg, chn, y),
                         fg_data);
                    fg_data_ptr = fg_data;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    fg_data_ptr = FF_ImgFloatRow (&fgimg->pimg, chn, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
            switch (bgimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (&bgimg->pimg, chn, y),
                         bg_data);
                    bg_data_ptr = bg_data;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    bg_data_ptr = FF_ImgFloatRow (&bgimg->pimg, chn, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
            switch (cmimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    cm_data_ptr = cm_data;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    cm_data_ptr = FF_ImgFloatRow (&cmimg->pimg, chn, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
            crosspixels (width, mix, fg_data_ptr, bg_data_ptr, cm_data_ptr);
            switch (cmimg->pimg.channel[chn]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyFloat2UByte
                        (width,
                         cm_data_ptr,
                         FF_ImgUByteRow (&cmimg->pimg, chn, y));
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    break;
                }
                default: {
                    UT_ErrFatal ("conc_crossimage", "invalid channel type");
                }
            }
        }
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_CompSlowAlpha
 *
 *      Usage:          Composite two images, using a black and white matte,
 *                      slow version.
 *
 *      Synopsis:       UT_Bool IP_CompSlowAlpha
 *                              (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_CompSlowAlpha
        (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)
{
    Float32     ub_fg_lin[256],
                ub_bg_lin[256],
                f_fg_lin[GTABSIZE],
                f_bg_lin[GTABSIZE],
                f_lin_cm[GTABSIZE];
    Int32               nel;
    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 (!IP_UByteGammaTable2 (fgimg->pimg.desc.gamma, ub_fg_lin) ||
        !IP_UByteGammaTable2 (bgimg->pimg.desc.gamma, ub_bg_lin) ||
        !IP_FloatGammaTable (fgimg->pimg.desc.gamma, f_fg_lin) ||
        !IP_FloatGammaTable (bgimg->pimg.desc.gamma, f_bg_lin) ||
        !IP_FloatGammaTable (1.0 / cmimg->pimg.desc.gamma, f_lin_cm))
        return UT_False;

    /* 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);

    /* Allocate memory for intermediate pixel buffers. */

    nel = IP_StateNumThreads * parms.width;
    if (!(parms.front = UT_MemTempArray (nel, Float32)) ||
        !(parms.back = UT_MemTempArray (nel, Float32)) ||
        !(parms.trans = UT_MemTempArray (nel, Float32)) ||
        !(parms.comp = UT_MemTempArray (nel, Float32)))
        return UT_False;

    /* Do the compositing. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mtimg = mtimg;
    parms.f_r = f_r;
    parms.f_g = f_g;
    parms.f_b = f_b;
    parms.b_r = b_r;
    parms.b_g = b_g;
    parms.b_b = b_b;
    parms.ub_fg_lin = ub_fg_lin;
    parms.ub_bg_lin = ub_bg_lin;
    parms.f_fg_lin = f_fg_lin;
    parms.f_bg_lin = f_bg_lin;
    parms.f_lin_cm = f_lin_cm;
    return UT_THREAD_EXEC (conc_comp_bwmatte,
                                &parms, IP_StateNumThreads, stacksize);
}

/***************************************************************************
 *[@e
 *      Name:           IP_CompSlowColorMatte
 *
 *      Usage:          Composite two images, using a colored matte,
 *                      slow version.
 *
 *      Synopsis:       UT_Bool IP_CompSlowColorMatte
 *                              (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_CompSlowColorMatte
        (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)
{
    Float32     ub_fg_lin[256],
                ub_bg_lin[256],
                f_fg_lin[GTABSIZE],
                f_bg_lin[GTABSIZE],
                f_lin_cm[GTABSIZE];
    Int32               nel;
    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 (!IP_UByteGammaTable2 (fgimg->pimg.desc.gamma, ub_fg_lin) ||
        !IP_UByteGammaTable2 (bgimg->pimg.desc.gamma, ub_bg_lin) ||
        !IP_FloatGammaTable (fgimg->pimg.desc.gamma, f_fg_lin) ||
        !IP_FloatGammaTable (bgimg->pimg.desc.gamma, f_bg_lin) ||
        !IP_FloatGammaTable (1.0 / cmimg->pimg.desc.gamma, f_lin_cm))
        return UT_False;

    /* 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);

    /* Allocate memory for intermediate pixel buffers. */

    nel = IP_StateNumThreads * parms.width;
    if (!(parms.front = UT_MemTempArray (nel, Float32)) ||
        !(parms.back = UT_MemTempArray (nel, Float32)) ||
        !(parms.r_trans = UT_MemTempArray (nel, Float32)) ||
        !(parms.g_trans = UT_MemTempArray (nel, Float32)) ||
        !(parms.b_trans = UT_MemTempArray (nel, Float32)) ||
        !(parms.comp = UT_MemTempArray (nel, Float32)))
        return UT_False;

    /* Do the compositing. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mtimg = mtimg;
    parms.f_r = f_r;
    parms.f_g = f_g;
    parms.f_b = f_b;
    parms.b_r = b_r;
    parms.b_g = b_g;
    parms.b_b = b_b;
    parms.ub_fg_lin = ub_fg_lin;
    parms.ub_bg_lin = ub_bg_lin;
    parms.f_fg_lin = f_fg_lin;
    parms.f_bg_lin = f_bg_lin;
    parms.f_lin_cm = f_lin_cm;
    return UT_THREAD_EXEC (conc_comp_clrmatte,
                                &parms, IP_StateNumThreads, stacksize);
}

/***************************************************************************
 *[@e
 *      Name:           IP_CompSlowDepth
 *
 *      Usage:          Composite two images, using depth mattes,
 *                      slow version.
 *
 *      Synopsis:       UT_Bool IP_CompSlowDepth
 *                              (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_CompSlowDepth (IP_ImageId fgimg, IP_ImageId bgimg, IP_ImageId cmimg)
{
    Int32               nel;
    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);

    /* Allocate memory for intermediate pixel buffers. */

    nel = IP_StateNumThreads * parms.width;
    if (!(parms.fg_data = UT_MemTempArray (nel, Float32)) ||
        !(parms.bg_data = UT_MemTempArray (nel, Float32)) ||
        !(parms.cm_data = UT_MemTempArray (nel, Float32)) ||
        !(parms.fg_depth = UT_MemTempArray (nel, Float32)) ||
        !(parms.bg_depth = UT_MemTempArray (nel, Float32)))
        return UT_False;

    /* 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_CrossDissolveSlow
 *
 *      Usage:          Crosss-dissolve between two images, slow version.
 *
 *      Synopsis:       UT_Bool IP_CrossDissolveSlow
 *                              (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_CrossDissolveSlow
        (IP_ImageId fgimg, IP_ImageId bgimg, IP_ImageId cmimg, Float32 mix)
{
    Float32     ub_fg_lin[256],
                ub_bg_lin[256],
                f_fg_lin[GTABSIZE],
                f_bg_lin[GTABSIZE],
                f_lin_cm[GTABSIZE];
    Int32               nel;
    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;
    }
    if (!IP_UByteGammaTable2 (fgimg->pimg.desc.gamma, ub_fg_lin) ||
        !IP_UByteGammaTable2 (bgimg->pimg.desc.gamma, ub_bg_lin) ||
        !IP_FloatGammaTable (fgimg->pimg.desc.gamma, f_fg_lin) ||
        !IP_FloatGammaTable (bgimg->pimg.desc.gamma, f_bg_lin) ||
        !IP_FloatGammaTable (1.0 / cmimg->pimg.desc.gamma, f_lin_cm))
        return UT_False;

    /* 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);

    /* Allocate memory for intermediate pixel buffers. */

    nel = IP_StateNumThreads * parms.width;
    if (!(parms.fg_data = UT_MemTempArray (nel, Float32)) ||
        !(parms.bg_data = UT_MemTempArray (nel, Float32)) ||
        !(parms.cm_data = UT_MemTempArray (nel, Float32)))
        return UT_False;

    /* Do the cross-dissolve. */

    parms.fgimg = fgimg;
    parms.bgimg = bgimg;
    parms.cmimg = cmimg;
    parms.mix = mix;
    parms.ub_fg_lin = ub_fg_lin;
    parms.ub_bg_lin = ub_bg_lin;
    parms.f_fg_lin = f_fg_lin;
    parms.f_bg_lin = f_bg_lin;
    parms.f_lin_cm = f_lin_cm;
    return UT_THREAD_EXEC (conc_crossimage,
                                &parms, IP_StateNumThreads, stacksize);
}
