/**************************************************************************
 *{@C
 *      Copyright:      1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Draw.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions to draw into images.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_DrawPixel
 *                      IP_DrawLine
 *                      IP_DrawRect
 *                      IP_DrawEllipse
 *                      IP_DrawCircle
 *                      IP_DrawBrushLine
 *                      IP_DrawBrush
 *                      IP_MergeBrush
 *                      IP_DrawText
 *                      IP_GetTextSize
 *                      IP_Blank
 *                      IP_BlankRect
 *                      IP_GetPixel
 *                      IP_CopyRect
 *                      IP_WrapRect
 *                      IP_CopyChannel
 *                      IP_CopyImage
 *
 **************************************************************************/

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

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

#define  USE_IMG_MACROS
#include "FF_Image.h"

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


/* Draw a point at pixel (x, y) in image "img". Only those channels in the
   image, for which the current drawing mask is UT_True, are affected.
   The current drawing mode selects, how the pixel's old color is combined with
   the current drawing color to form the new color for the pixel.
   Note: For maximum speed, this function does not check, if x and y are
         within the image area. Use drawPixelSafe for a safe, but slower
         version for pixel drawing.
*/

static void drawPixel (IP_ImageId img, Int32 x, Int32 y)
{
    Int32   i, iclr;
    UInt8   *ubpixel;
    Float32 *fpixel;

    for (i = 0; i < FF_NumImgChanTypes; ++i) {
        if (IP_StateDrawMask[i]) {
            switch (img->pimg.channel[i]) {
                case FF_ImgFmtTypeNone:
                    break;
                case FF_ImgFmtTypeUByte: {
                    ubpixel = FF_ImgUBytePixel (&img->pimg, i, x, y);
                    switch (IP_StateDrawMode[i]) {
                        case IP_DrawModeReplace:
                            *ubpixel = IP_StateDrawColorUByte[i];
                            break;
                        case IP_DrawModeAdd:
                            iclr = *ubpixel + IP_StateDrawColorUByte[i];
                            *ubpixel = UT_MAX (0, UT_MIN (iclr, 255));
                            break;
                        case IP_DrawModeSub:
                            iclr = *ubpixel - IP_StateDrawColorUByte[i];
                            *ubpixel = UT_MAX (0, UT_MIN (iclr, 255));
                            break;
                        case IP_DrawModeXor:
                            *ubpixel ^= IP_StateDrawColorUByte[i];
                            break;
                        default:
                            UT_ErrFatal ("drawPixel", "unknown draw mode");
                    }
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    fpixel = FF_ImgFloatPixel (&img->pimg, i, x, y);
                    switch (IP_StateDrawMode[i]) {
                        case IP_DrawModeReplace:
                            *fpixel = IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeAdd:
                            *fpixel += IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeSub:
                            *fpixel -= IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeXor:
                            iclr = (Int32) (*fpixel * 255.0);
                            *fpixel = (iclr ^ IP_StateDrawColorUByte[i]) *
                                        (1.0 / 255.0);
                            break;
                        default:
                            UT_ErrFatal ("drawPixel", "unknown draw mode");
                    }
                    break;
                }
                default: {
                    UT_ErrFatal ("drawPixel", "unknown pixel data format");
                }
            }
        }
    }
    return;
}

/* See comment of function drawPixel. */

static void drawPixelSafe (IP_ImageId img, Int32 x, Int32 y) {
    if ((x < 0) || (x >= img->pimg.desc.width) ||
        (y < 0) || (y >= img->pimg.desc.height)) {
         return;
    }
    drawPixel (img, x, y);
}

/* Draw a horizontal line from pixel (x1, y) to pixel (x2, y) in image "img".
   Only those channels in the image, for which the current drawing mask is 
   UT_True, are affected. The current drawing mode selects, how the pixels' old
   colors are combined with the current drawing color to form the new pixel
   colors. */

static void draw_span (IP_ImageId img, Int32 x1, Int32 x2, Int32 y)
{
    Int32       i, iclr;
    UInt8       *ubpixel, *ublast;
    Float32     *fpixel, *flast;

    for (i = 0; i < FF_NumImgChanTypes; ++i) {
        if (IP_StateDrawMask[i]) {
            switch (img->pimg.channel[i]) {
                case FF_ImgFmtTypeNone:
                    break;
                case FF_ImgFmtTypeUByte: {
                    ubpixel = FF_ImgUByteRow (&img->pimg, i, y);
                    ublast = ubpixel + x2;
                    ubpixel += x1;
                    switch (IP_StateDrawMode[i]) {
                        case IP_DrawModeReplace:
                            while (ubpixel <= ublast)
                                *(ubpixel++) = IP_StateDrawColorUByte[i];
                            break;
                        case IP_DrawModeAdd:
                            while (ubpixel <= ublast) {
                                iclr = *ubpixel + IP_StateDrawColorUByte[i];
                                *(ubpixel++) = UT_MAX (0, UT_MIN (iclr, 255));
                            }
                            break;
                        case IP_DrawModeSub:
                            while (ubpixel <= ublast) {
                                iclr = *ubpixel - IP_StateDrawColorUByte[i];
                                *(ubpixel++) = UT_MAX (0, UT_MIN (iclr, 255));
                            }
                            break;
                        case IP_DrawModeXor:
                            while (ubpixel <= ublast)
                                *(ubpixel++) ^= IP_StateDrawColorUByte[i];
                            break;
                        default:
                            UT_ErrFatal ("draw_span", "unknown draw mode");
                    }
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    fpixel = FF_ImgFloatRow (&img->pimg, i, y);
                    flast = fpixel + x2;
                    fpixel += x1;
                    switch (IP_StateDrawMode[i]) {
                        case IP_DrawModeReplace:
                            while (fpixel <= flast)
                                *(fpixel++) = IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeAdd:
                            while (fpixel <= flast)
                                *(fpixel++) += IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeSub:
                            while (fpixel <= flast)
                                *(fpixel++) -= IP_StateDrawColorFloat[i];
                            break;
                        case IP_DrawModeXor:
                            while (fpixel <= flast) {
                                iclr = (Int32) (*fpixel * 255.0);
                                *(fpixel++) = (iclr ^ IP_StateDrawColorUByte[i]) *
                                                (1.0 / 255.0);
                            }
                            break;
                        default:
                            UT_ErrFatal ("draw_span", "unknown draw mode");
                    }
                    break;
                }
                default: {
                    UT_ErrFatal ("draw_span", "unknown pixel data format");
                }
            }
        }
    }
    return;
}

/* Clip a brush with width "wb" and height "hb" against an image with width
   "wi" and height "hi". The brush should be applied at image position (xi, yi).
   If the brush lies totally outside of the image the function returns UT_True
   and the result parameters are undefined.
   If at least one pixel of the brush lies inside the image, the result
   parameters are filled with the clipped min. and max. ranges that can be
   used of the brush and the image.
   The result parameters are ready for use with functions *_brush_span.
*/
static UT_Bool clip_brush 
        (Int32 wb, Int32 hb, Int32 wi, Int32 hi, Int32 xi, Int32 yi, 
         Int32 *cxi, Int32 *cyi, 
         Int32 *cxb, Int32 *cxlen, Int32 *cyb1, Int32 *cyb2)
{
    Int32 xb1, xb2, yb1, yb2;
    Int32 xi1, yi1;

    xb1 = 0; xb2 = wb - 1;
    yb1 = 0; yb2 = wb - 1;

    xi1 = xi;
    yi1 = yi;

    if (xi < 0) {
        xi1 = 0;
        xb1 = -xi;
        if (xb1 >= wb) 
            return UT_True;
    }
    if (yi < 0) {
        yi1 = 0;
        yb1 = -yi;
        if (yb1 >= hb) 
            return UT_True;
    }
    if (xi + wb > wi) {
        xb2 = wi - xi - 1;
        if (xb2 < 0) 
            return UT_True;
    }
    if (yi + hb > hi) {
        yb2 = hi - yi - 1;
        if (yb2 < 0) 
            return UT_True;
    }

    *cxi = xi1;
    *cyi = yi1;
    *cxb = xb1;
    *cxlen = xb2 - xb1 + 1;
    *cyb1 = yb1;
    *cyb2 = yb2;
    return UT_False;
}

/* Draw a horizontal brush span into image "img" starting at pixel (xi, yi).
   The brush span is defined in image "bsh" and starts at pixel (xb, yb).
   The span length is "slen". The intensity (pressure) of the brush is
   specified in "intensity".
   Only those channels in the image, for which the 
   current drawing mask is UT_True, are affected. 
*/
static void draw_brush_span (IP_ImageId img, IP_ImageId bsh, 
                             Int32 xi, Int32 yi, Int32 xb, Int32 yb, 
                             Int32 slen, Float32 intensity)
{
    Int32 i;
    Float32 *bshpix;

    for (i=0; i<FF_NumImgChanTypes; i++) {
        if (IP_StateDrawMask[i]) {
            bshpix = FF_ImgFloatRow (&bsh->pimg, FF_ImgChanTypeGray, yb);
            bshpix += xb;

            switch (img->pimg.channel[i]) {
                case FF_ImgFmtTypeNone:
                    break;
                case FF_ImgFmtTypeUByte: {
                    UInt8 *imgpix, *imglast;
                    Float32 fclr;

                    imgpix = FF_ImgUByteRow (&img->pimg, i, yi);
                    imgpix += xi;
                    imglast = imgpix + slen -1;

                    while (imgpix <= imglast) {
                        fclr = (((1.0 - *bshpix) * *imgpix) +
                                 (intensity * *bshpix * IP_StateDrawColorUByte[i]));
                        *imgpix = UT_MAX (0, UT_MIN ((Int32)fclr, 255));
                        imgpix++;
                        bshpix++;
                    }
                    break;
                }
                case FF_ImgFmtTypeFloat: {

                    Float32 *imgpix, *imglast;

                    imgpix = FF_ImgFloatRow (&img->pimg, i, yi);
                    imgpix += xi;
                    imglast = imgpix + slen -1;

                    while (imgpix <= imglast) {
                        *imgpix = (((1.0 - *bshpix) * *imgpix) +
                                    (intensity * *bshpix * IP_StateDrawColorFloat[i]));
                        imgpix++;
                        bshpix++;
                    }
                    break;
                }
                default: {
                    UT_ErrFatal ("draw_brush_span", "unknown pixel data format");
                }
            }
        }
    }
    return;
}

/* Draw a horizontal merge brush span from image "backimg" into image "foreimg"
   starting at pixel (xi, yi).
   The brush span is defined in image "bsh" and starts at pixel (xb, yb).
   The span length is "slen". The intensity (pressure) of the brush is
   specified in "intensity".
   Only those channels in the images, for which the 
   current drawing mask is UT_True, are affected. 
*/
static void merge_brush_span (IP_ImageId backimg, IP_ImageId foreimg, 
                              IP_ImageId bsh, 
                              Int32 xi, Int32 yi, Int32 xb, Int32 yb, 
                              Int32 slen, Float32 intensity)
{
    Int32 i;
    Float32 *bshpix;

    for (i=0; i<FF_NumImgChanTypes; i++) {
        if (IP_StateDrawMask[i]) {
            bshpix = FF_ImgFloatRow (&bsh->pimg, FF_ImgChanTypeGray, yb);
            bshpix += xb;

            switch (backimg->pimg.channel[i]) {
                case FF_ImgFmtTypeNone:
                    break;
                case FF_ImgFmtTypeUByte: {
                    switch (foreimg->pimg.channel[i]) {
                        case FF_ImgFmtTypeNone: {
                            break;
                        }
                        case FF_ImgFmtTypeUByte: {
                            UInt8 *fpix, *bpix, *blast;
                            Float32 fclr;

                            bpix = FF_ImgUByteRow (&backimg->pimg, i, yi);
                            bpix += xi;
                            blast = bpix + slen -1;

                            fpix = FF_ImgUByteRow (&foreimg->pimg, i, yi);
                            fpix += xi;

                            while (bpix <= blast) {
                                fclr = (((1.0 - *bshpix) * *fpix) +
                                         (intensity * *bshpix * *bpix));
                                *fpix = UT_MAX (0, UT_MIN ((Int32)fclr, 255));
                                bpix++;
                                fpix++;
                                bshpix++;
                            }
                            break;
                        }
                        case FF_ImgFmtTypeFloat: {
                            UInt8 *bpix, *blast;
                            Float32 *fpix;

                            bpix = FF_ImgUByteRow (&backimg->pimg, i, yi);
                            bpix += xi;
                            blast = bpix + slen -1;

                            fpix = FF_ImgFloatRow (&foreimg->pimg, i, yi);
                            fpix += xi;

                            while (bpix <= blast) {
                                *fpix = (((1.0 - *bshpix) * *fpix) +
                                         (intensity * *bshpix * 
                                          *bpix * (1.0 / 255.0)));
                                bpix++;
                                fpix++;
                                bshpix++;
                            }
                            break;
                        }
                    }
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    switch (foreimg->pimg.channel[i]) {
                        case FF_ImgFmtTypeNone: {
                            break;
                        }
                        case FF_ImgFmtTypeUByte: {
                            Float32 *bpix, *blast, fclr;
                            UInt8 *fpix;

                            bpix = FF_ImgFloatRow (&backimg->pimg, i, yi);
                            bpix += xi;
                            blast = bpix + slen -1;

                            fpix = FF_ImgUByteRow (&foreimg->pimg, i, yi);
                            fpix += xi;

                            while (bpix <= blast) {
                                fclr = (((1.0 - *bshpix) * *fpix) +
                                         (intensity * *bshpix * *bpix * 255.0));
                                *fpix = UT_MAX (0, UT_MIN ((Int32)fclr, 255));
                                bpix++;
                                fpix++;
                                bshpix++;
                            }
                             break;
                        }
                        case FF_ImgFmtTypeFloat: {
                            Float32 *fpix, *bpix, *blast;

                            bpix = FF_ImgFloatRow (&backimg->pimg, i, yi);
                            bpix += xi;
                            blast = bpix + slen -1;

                            fpix = FF_ImgFloatRow (&foreimg->pimg, i, yi);
                            fpix += xi;

                            while (bpix <= blast) {
                                *fpix = (((1.0 - *bshpix) * *fpix) +
                                         (intensity * *bshpix * *bpix));
                                bpix++;
                                fpix++;
                                bshpix++;
                            }
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
    return;
}

/* Move the pixels from (xs1, ys) to (xs2, ys) in image "img1" into image
   "img2", so that pixel (xs1, ys) from "img1" goes into pixel (xd1, yd) in
   "img2". Only those channels in "img2", for which the current drawing mask is
   UT_True, are affected. The current drawing mode selects, how the colors in
   "img2" are combined with the colors from "img1" to form new pixel colors. */

static void move_span
        (IP_ImageId img1, IP_ImageId img2,
         Int32 xs1, Int32 xs2, Int32 ys,
         Int32 xd1, Int32 yd)
{
    Int32       i, xs, xd, xinc, iclr;
    UInt8       *ubsrc, *ubdest;
    Float32     *fsrc, *fdest;

    xinc = (xd1 > xs1)? -1: 1;
    for (i = 0; i < FF_NumImgChanTypes; ++i) {
        if (IP_StateDrawMask[i]) {
            switch (img1->pimg.channel[i]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte:
                    ubsrc = FF_ImgUByteRow (&img1->pimg, i, ys);
                    switch (img2->pimg.channel[i]) {
                        case FF_ImgFmtTypeNone: {
                            break;
                        }
                        case FF_ImgFmtTypeUByte:
                            ubdest = FF_ImgUByteRow (&img2->pimg, i, yd);
                            xs = xs1;
                            xd = xd1;
                            while (xs != xs2 + xinc) {
                                switch (IP_StateDrawMode[i]) {
                                    case IP_DrawModeReplace:
                                        ubdest[xd] = ubsrc[xs];
                                        break;
                                    case IP_DrawModeAdd:
                                        iclr = ubdest[xd] + ubsrc[xs];
                                        ubdest[xd] = UT_MIN (iclr, 255);
                                        break;
                                    case IP_DrawModeSub:
                                        iclr = ubdest[xd] - ubsrc[xs];
                                        ubdest[xd] = UT_MAX (0, iclr);
                                        break;
                                    case IP_DrawModeXor:
                                        ubdest[xd] ^= ubsrc[xs];
                                        break;
                                }
                                xs += xinc;
                                xd += xinc;
                            }
                            break;
                        case FF_ImgFmtTypeFloat:
                            fdest = FF_ImgFloatRow (&img2->pimg, i, yd);
                            xs = xs1;
                            xd = xd1;
                            while (xs != xs2 + xinc) {
                                switch (IP_StateDrawMode[i]) {
                                    case IP_DrawModeReplace:
                                        fdest[xd] =
                                            (1.0 / 255.0) * ubsrc[xs];
                                        break;
                                    case IP_DrawModeAdd:
                                        fdest[xd] +=
                                            (1.0 / 255.0) * ubsrc[xs];
                                        break;
                                    case IP_DrawModeSub:
                                        fdest[xd] -=
                                            (1.0 / 255.0) * ubsrc[xs];
                                        break;
                                    case IP_DrawModeXor:
                                        fdest[xd] = (1.0 / 255.0) *
                                            ((Int32) (255.0 * fdest[xd]) ^
                                             ubsrc[xs]);
                                        break;
                                }
                                xs += xinc;
                                xd += xinc;
                            }
                            break;
                    }
                    break;
                case FF_ImgFmtTypeFloat:
                    fsrc = FF_ImgFloatRow (&img1->pimg, i, ys);
                    switch (img2->pimg.channel[i]) {
                        case FF_ImgFmtTypeNone: {
                            break;
                        }
                        case FF_ImgFmtTypeUByte:
                            ubdest = FF_ImgUByteRow (&img2->pimg, i, yd);
                            xs = xs1;
                            xd = xd1;
                            while (xs != xs2 + xinc) {
                                switch (IP_StateDrawMode[i]) {
                                    case IP_DrawModeReplace:
                                        iclr = (Int32) (fsrc[xs] * 255.0);
                                        ubdest[xd] =
                                            UT_MAX (0, UT_MIN (iclr, 255));
                                        break;
                                    case IP_DrawModeAdd:
                                        iclr = (Int32) (fsrc[xs] * 255.0);
                                        iclr += ubdest[xd];
                                        ubdest[xd] =
                                            UT_MAX (0, UT_MIN (iclr, 255));
                                        break;
                                    case IP_DrawModeSub:
                                        iclr = (Int32) (fsrc[xs] * 255.0);
                                        iclr -= ubdest[xd];
                                        ubdest[xd] =
                                            UT_MAX (0, UT_MIN (iclr, 255));
                                        break;
                                    case IP_DrawModeXor:
                                        iclr = (Int32) (fsrc[xs] * 255.0);
                                        iclr ^= ubdest[xd];
                                        ubdest[xd] =
                                            UT_MAX (0, UT_MIN (iclr, 255));
                                        break;
                                }
                                xs += xinc;
                                xd += xinc;
                            }
                            break;
                        case FF_ImgFmtTypeFloat:
                            fdest = FF_ImgFloatRow (&img2->pimg, i, yd);
                            xs = xs1;
                            xd = xd1;
                            while (xs != xs2 + xinc) {
                                switch (IP_StateDrawMode[i]) {
                                    case IP_DrawModeReplace:
                                        fdest[xd] = fsrc[xs];
                                        break;
                                    case IP_DrawModeAdd:
                                        fdest[xd] += fsrc[xs];
                                        break;
                                    case IP_DrawModeSub:
                                        fdest[xd] -= fsrc[xs];
                                        break;
                                    case IP_DrawModeXor:
                                        fdest[xd] = (1.0 / 255.0) *
                                            ((Int32) (255.0 * fdest[xd]) ^
                                             (Int32) (255.0 * fsrc[xs]));
                                        break;
                                }
                                xs += xinc;
                                xd += xinc;
                            }
                            break;
                    }
                    break;
            }
        }
    }
    return;
}

/* Convert the value of all channels of pixel (x, y) in image "img" to floating
   point format and write the converted data to "clr". */

static void get_pixel (const IP_ImageId img, Int32 x, Int32 y, Float32 clr[])
{
    Int32 i;

    for (i = 0; i < FF_NumImgChanTypes; ++i) {
        switch (img->pimg.channel[i]) {
            case FF_ImgFmtTypeNone: {
                clr[i] = 0.0;
                break;
            }
            case FF_ImgFmtTypeUByte: {
                clr[i] = *FF_ImgUBytePixel (&img->pimg, i, x, y) *
                        (1.0 / 255.0);
                break;
            }
            case FF_ImgFmtTypeFloat: {
                clr[i] = *FF_ImgFloatPixel (&img->pimg, i, x, y);
                break;
            }
            default: {
                UT_ErrFatal ("get_pixel", "unknown pixel data format");
            }
        }
    }
    return;
}

static void bresenham (IP_ImageId img, Int32 x1, Int32 y1, Int32 x2, Int32 y2, 
                       Int32 n, void (*drawfunct) (IP_ImageId, Int32, Int32))
{
    Int32 xclipmin, xclipmax, yclipmin, yclipmax,
          xt, yt, dx, dy, twodx, twody, xdir, ydir,
          eps, delta, s, t, s1, t1, nth;

    if (n <= 0)
        n = 1;

    /* Clip the line along the bottom and top, and then at the
    left and right borders of the image */

    xclipmin = 0;
    xclipmax = img->pimg.desc.width - 1;
    yclipmin = 0;
    yclipmax = img->pimg.desc.height - 1;
    if (y1 > y2) {
        UT_SWAP (y1, y2, Int32);
        UT_SWAP (x1, x2, Int32);
    }
    if (y1 > yclipmax) {
        return;
    } else if (y2 > yclipmax) {
        s = y2 - yclipmax;
        t = yclipmax - y1;
        dy = y2 - y1;
        y2 = yclipmax;
        x2 = (x1 * s + x2 * t) / dy;
    }
    if (y2 < yclipmin) {
        return;
    } else if (y1 < yclipmin) {
        s = y2 - yclipmin;
        s1 = s + 1;
        t = yclipmin - y1;
        t1 = t - 1;
        dy = y2 - y1;
        y1 = yclipmin;
        xt = (x1 * s1 + x2 * t1) / dy;
        x1 = (x1 * s + x2 * t) / dy;
        x1 = xt + UT_SGN (x1 - xt);
    }
    if (x1 > x2) {
        UT_SWAP (y1, y2, Int32);
        UT_SWAP (x1, x2, Int32);
    }
    if (x1 > xclipmax) {
        return;
    } else if (x2 > xclipmax) {
        s = x2 - xclipmax;
        t = xclipmax - x1;
        dx = x2 - x1;
        x2 = xclipmax;
        y2 = (y1 * s + y2 * t) / dx;
    }
    if (x2 < xclipmin) {
        return;
    } else if (x1 < xclipmin) {
        s = x2 - xclipmin;
        s1 = s + 1;
        t = xclipmin - x1;
        t1 = t - 1;
        dx = x2 - x1;
        x1 = xclipmin;
        yt = (y1 * s1 + y2 * t1) / dx;
        y1 = (y1 * s + y2 * t) / dx;
        y1 = yt + UT_SGN (y1 - yt);
    }

    /* Draw the line, using Bresenham's algorithm, if at least part
    of the line remained after clipping. */

    dx = x2 - x1;
    dy = y2 - y1;
    xdir = UT_SGN (dx);
    ydir = UT_SGN (dy);
    dx = UT_ABS (dx);
    dy = UT_ABS (dy);
    twodx = dx << 1;
    twody = dy << 1;
    nth = n;
    if (dy <= dx) {
        eps = twody - dx;
        delta = twody - twodx;
        while (x1 != x2) {
            if (--nth == 0) {
                drawfunct (img, x1, y1);
                nth = n;
            }
            if (eps > 0) {
                y1 += ydir;
                eps += delta;
            } else {
                eps += twody;
            }
            x1 += xdir;
        }
    } else {
        eps = twodx - dy;
        delta = twodx - twody;
        while (y1 != y2) {
            if (--nth == 0) {
                drawfunct (img, x1, y1);
                nth = n;
            }
            if (eps > 0) {
                x1 += xdir;
                eps += delta;
            } else {
                eps += twodx;
            }
            y1 += ydir;
        }
    }
    drawfunct (img, x2, y2);
    return;
}

/* Draw the outline of a circle using the 8-way symmetry algorithm.
 * Description of the algorithm and Java source code can be found at
 * http://www.cs.unc.edu/~mcmillan/comp136/Lecture7/circle.html
 */
static void drawCircleOutline (IP_ImageId img, Int32 cx, Int32 cy, Int32 r)
{
    Int32 x, y, r2;

    r2 = r * r;
    drawPixelSafe (img, cx, cy + r);
    drawPixelSafe (img, cx, cy - r);
    drawPixelSafe (img, cx + r, cy);
    drawPixelSafe (img, cx - r, cy);

    y = r;
    x = 1;
    y = (Int32) (sqrt(r2 - 1) + 0.5);
    while (x < y) {
        drawPixelSafe (img, cx + x, cy + y);
        drawPixelSafe (img, cx + x, cy - y);
        drawPixelSafe (img, cx - x, cy + y);
        drawPixelSafe (img, cx - x, cy - y);
        drawPixelSafe (img, cx + y, cy + x);
        drawPixelSafe (img, cx + y, cy - x);
        drawPixelSafe (img, cx - y, cy + x);
        drawPixelSafe (img, cx - y, cy - x);
        x++;
        y = (Int32) (sqrt(r2 - x*x) + 0.5);
    }
    if (x == y) {
        drawPixelSafe (img, cx + x, cy + y);
        drawPixelSafe (img, cx + x, cy - y);
        drawPixelSafe (img, cx - x, cy + y);
        drawPixelSafe (img, cx - x, cy - y);
    }
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawPixel
 *
 *      Usage:          Draws a point at pixel (x, y) into an image.
 *
 *      Synopsis:       void IP_DrawPixel (IP_ImageId img, Int32 x, Int32 y)
 *
 *      Description:    Draws a point at pixel (x, y) in image "img".
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawAAPixel
 *                      IP_DrawLine
 *                      IP_GetPixel
 *
 ***************************************************************************/

void IP_DrawPixel (IP_ImageId img, Int32 x, Int32 y)
{
    drawPixelSafe (img, x, y);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawLine
 *
 *      Usage:          Draws a line into an image.
 *
 *      Synopsis:       void IP_DrawLine
 *                              (IP_ImageId img, Int32 x1, Int32 y1,
 *                               Int32 x2, Int32 y2, Int32 stipple)
 *
 *      Description:    Draws a line from pixel (x1, y1) to pixel (x2, y2)
 *                      in image "img".
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawPixel
 *                      IP_DrawBrushLine
 *                      IP_DrawAALine
 *
 ***************************************************************************/

void IP_DrawLine (IP_ImageId img, Int32 x1, Int32 y1, 
                  Int32 x2, Int32 y2, Int32 stipple)
{
    bresenham (img, x1, y1, x2, y2, stipple, drawPixel);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawRect
 *
 *      Usage:          Draw a filled rectangle into an image.
 *
 *      Synopsis:       void IP_DrawRect
 *                              (IP_ImageId img, Int32 x1, Int32 y1,
 *                               Int32 x2, Int32 y2)
 *
 *      Description:    Draw a filled rectangle into image "img". One corner
 *                      of the rectangle is located at pixel (x1, y1). The
 *                      diagonally opposite corner is at pixel (x2, y2).
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawLine
 *
 ***************************************************************************/

void IP_DrawRect (IP_ImageId img, Int32 x1, Int32 y1, 
                  Int32 x2, Int32 y2, UT_Bool fill)
{
    Int32 xmin, ymin, xmax, ymax, y;

    xmin = UT_MAX (0, UT_MIN (x1, x2));
    ymin = UT_MAX (0, UT_MIN (y1, y2));
    xmax = UT_MIN (UT_MAX (x1, x2), img->pimg.desc.width - 1);
    ymax = UT_MIN (UT_MAX (y1, y2), img->pimg.desc.height - 1);

    if (fill) {
        for (y = ymin; y <= ymax; ++y) {
            draw_span (img, xmin, xmax, y);
        }
    } else {
        IP_DrawLine (img, xmin, ymin, xmin, ymax, 1);
        IP_DrawLine (img, xmax, ymin, xmax, ymax, 1);
        IP_DrawLine (img, xmin, ymin, xmax, ymin, 1);
        IP_DrawLine (img, xmin, ymax, xmax, ymax, 1);
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawEllipse
 *
 *      Usage:          Draw an ellipse into an image.
 *
 *      Synopsis:       void IP_DrawEllipse
 *                              (IP_ImageId img, Int32 cx, Int32 cy,
 *                               Int32 rx, Int32 ry, UT_Bool fill)
 *
 *      Description:    Draw a filled ellipse into image "img", if "fill" is 
 *                      set to UT_True, otherwise draw an ellipse outline.
 *                      The horizontal radius is given with "rx", the 
 *                      vertical radius with "ry".
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *                      Note: Outline drawing is currently not supported.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawRect
 *                      IP_DrawCircle
 *
 ***************************************************************************/

void IP_DrawEllipse (IP_ImageId img, Int32 cx, Int32 cy, 
                     Int32 rx, Int32 ry, UT_Bool fill)
{
    Int32   xmin, ymin, xmax, ymax, x, y;
    Float32 rx2, ry2;

    if (!fill) {
        return;
    }

    xmin = UT_MAX (0, cx-rx);
    ymin = UT_MAX (0, cy-ry);
    xmax = UT_MIN (cx+rx, img->pimg.desc.width  - 1);
    ymax = UT_MIN (cy+ry, img->pimg.desc.height - 1);
    rx2 = UT_SQR ((Float32)rx);
    ry2 = UT_SQR ((Float32)ry);
    for (x = xmin; x <= xmax; ++x) {
        for (y = ymin; y <= ymax; ++y) {
            if (UT_SQR(x-cx) / rx2 + UT_SQR(y-cy) / ry2 <= 1.0) {
                drawPixel (img, x, y);
            }
        }
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawCircle
 *
 *      Usage:          Draw a circle into an image.
 *
 *      Synopsis:       void IP_DrawCircle
 *                              (IP_ImageId img, Int32 cx, Int32 cy, Int32 r,
 *                              UT_Bool fill)
 *
 *      Description:    Draw a filled circle into image "img", if "fill" is 
 *                      set to UT_True, otherwise draw a circle outline.
 *                      The center of the circle is at pixel (cx, cy).
 *                      The radius is given with "rx".
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawRect
 *                      IP_DrawEllipse
 *
 ***************************************************************************/

void IP_DrawCircle (IP_ImageId img, Int32 cx, Int32 cy, Int32 r, UT_Bool fill)
{
    if (fill) {
        IP_DrawEllipse (img, cx, cy, r, r, fill);
    } else {
        drawCircleOutline (img, cx, cy, r);
    }
}

static IP_ImageId cur_brush;
static Float32 cur_intensity;

static void draw_brush_pixel (IP_ImageId img, Int32 x, Int32 y)
{
    IP_DrawBrush (img, cur_brush, x, y, cur_intensity);
}


/***************************************************************************
 *[@e
 *      Name:           IP_DrawBrush
 *
 *      Usage:          Make a brush stroke.
 *
 *      Synopsis:       UT_Bool IP_DrawBrush
 *                              (IP_ImageId img, IP_ImageId bsh,
 *                               Int32 xi, Int32 yi, Float32 intensity)
 *
 *      Description:    Draw a brush stroke into image "img" at pixel (xi, yi).
 *                      The brush is defined in image "bsh".
 *                      The intensity (or pressure) of the stroke is 
 *                      specified in "intensity". Valid intensity values
 *                      are in the range of (0.0, 1.0), where 1.0 means
 *                      full intensity.
 *                      The current drawing color and mask select which
 *                      channels in the image are affected, and how.
 *
 *                      Notes: 
 *                         - The brush shape is taken from the 
 *                           "FF_ImgChanTypeGray" channel of image "bsh".
 *                           This channel has to be in "FF_ImgFmtTypeFloat"
 *                           format with values in the range of (0.0, 1.0).
 *                         - The origin of the brush is the lower left corner
 *                           of the brush image, i.e. (xi, y1) = bsh[0][0].
 *
 *      Return value:   UT_True if successful, else UT_False.   
 *
 *      See also:       IP_DrawBrushLine
 *                      IP_MergeBrush
 *
 ***************************************************************************/

UT_Bool IP_DrawBrush (IP_ImageId img, IP_ImageId bsh,
                     Int32 xi, Int32 yi, Float32 intensity)
{
    Int32 y, slen;
    Int32 xi1, yi1, xb, yb1, yb2;
    Float32 intens;

    if (bsh->pimg.channel[FF_ImgChanTypeGray] != FF_ImgFmtTypeFloat) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                      FF_ImgGetChanTypeName (FF_ImgChanTypeGray));
        return UT_False;
    }

    if (clip_brush (bsh->pimg.desc.width, bsh->pimg.desc.height,
                    img->pimg.desc.width, img->pimg.desc.height,
                    xi, yi, &xi1, &yi1, &xb, &slen, &yb1, &yb2))
        return UT_True;

    intens = UT_MAX (0.0, UT_MIN (1.0, intensity));

    for (y=yb1; y<=yb2; y++) {
        draw_brush_span (img, bsh, xi1, yi1, xb, y, slen, intens);
        yi1++;
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawBrushLine
 *
 *      Usage:          Draw a brushed line into an image.
 *
 *      Synopsis:       void IP_DrawBrushLine
 *                              (IP_ImageId img, IP_ImageId bsh, 
 *                               Int32 x1, Int32 y1, Int32 x2, Int32 y2,
 *                               Int32 nstroke, Float32 intensity)
 *
 *      Description:    Draw a line from pixel (x1, y1) to pixel (x2, y2)
 *                      in image "img".
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawLine
 *                      IP_DrawBrush
 *                      IP_MergeBrush
 *
 ***************************************************************************/
void IP_DrawBrushLine (IP_ImageId img, IP_ImageId bsh, 
                          Int32 x1, Int32 y1, Int32 x2, Int32 y2,
                          Int32 nstroke, Float32 intensity)
{
    cur_brush = bsh;
    cur_intensity = intensity;
    bresenham (img, x1, y1, x2, y2, nstroke, draw_brush_pixel);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_MergeBrush
 *
 *      Usage:          Make a brush stroke to merge one image through another.
 *
 *      Synopsis:       UT_Bool IP_MergeBrush
 *                              (IP_ImageId backimg, IP_ImageId foreimg,
 *                               IP_ImageId bsh,
 *                               Int32 xi, Int32 yi, Float32 intensity)
 *
 *      Description:    Draw a brush stroke into image "foreimg" at 
 *                      pixel (xi, yi) based upon the color information
 *                      found in image "backimg".
 *                      The brush is defined in image "bsh".
 *                      The intensity (or pressure) of the stroke is 
 *                      specified in "intensity". Valid intensity values
 *                      are in the range of (0.0, 1.0), where 1.0 means
 *                      full intensity.
 *                      The current drawing mask select which
 *                      channels in the image are affected.
 *
 *      Return value:   UT_True if successful, else UT_False.   
 *
 *      See also:       IP_DrawBrush
 *                      IP_DrawBrushLine
 *
 ***************************************************************************/

UT_Bool IP_MergeBrush (IP_ImageId backimg, IP_ImageId foreimg,
                      IP_ImageId bsh, Int32 xi, Int32 yi, Float32 intensity)
{
    Int32 y, slen;
    Int32 xi1, yi1, xb, yb1, yb2;
    Float32 intens;

    if (bsh->pimg.channel[FF_ImgChanTypeGray] != FF_ImgFmtTypeFloat)
    {
        UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                      FF_ImgGetChanTypeName (FF_ImgChanTypeGray));
        return UT_False;
    }

    if (clip_brush (bsh->pimg.desc.width, bsh->pimg.desc.height,
                    UT_MIN (backimg->pimg.desc.width, foreimg->pimg.desc.width),
                    UT_MIN (backimg->pimg.desc.height, foreimg->pimg.desc.height),
                    xi, yi, &xi1, &yi1, &xb, &slen, &yb1, &yb2))
        return UT_True;

    intens = UT_MAX (0.0, UT_MIN (1.0, intensity));

    for (y=yb1; y<=yb2; y++)
    {
        merge_brush_span (backimg, foreimg, bsh, xi1, yi1, xb, y, slen, intens);
        yi1++;
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawText
 *
 *      Usage:          Draw a text string into an image.
 *
 *      Synopsis:       void IP_DrawText
 *                              (IP_ImageId img, Int32 x, Int32 y,
 *                               const char str[])
 *
 *      Description:    Draw a text string, "str", into image "img". The
 *                      lower left corner of the first character drawn is at
 *                      position (x, y).
 *                      The current drawing color, mode and mask select which
 *                      channels in the image are affected, and how.
 *
 *      Return value:   None.
 *
 *      See also:       IP_GetTextSize
 *
 ***************************************************************************/

void IP_DrawText (IP_ImageId img, Int32 x, Int32 y, const char str[])
{
    Int32       xletter, yletter, xstart, ystart, xend, yend;
    const char  *letter;
    const UInt8 *shape;

    xletter = x;
    yletter = y + (13 << IP_TextScale);
    letter = str;
    xstart = 0;
    ystart = 0;
    while (*letter) {
        shape = vecf_string (*letter);
        while (*shape) {
            xend = xletter + (vecf_x (shape) << IP_TextScale);
            yend = yletter - (vecf_y (shape) << IP_TextScale);
            if (vecf_draw (shape)) {
                IP_DrawLine (img, xstart, ystart, xend, yend, 1);
            }
            xstart = xend;
            ystart = yend;
            ++shape;
        }
        xletter += (8 << IP_TextScale);
        ++letter;
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetTextSize
 *
 *      Usage:          Get the width and height in pixels of a text string.
 *
 *      Synopsis:       void IP_GetTextSize
 *                              (const char str[], Int32 *width, Int32 *height)
 *
 *      Description:    Return the width and height, in pixels, of a text
 *                      string, "str".
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawText
 *
 ***************************************************************************/

void IP_GetTextSize (const char str[], Int32 *width, Int32 *height)
{
    *width = strlen (str) * (8 << IP_TextScale);
    *height = (15 << IP_TextScale);
    return;
}

Int32 IP_GetTextScale (void)
{
    return IP_TextScale;
}

void IP_SetTextScale (Int32 scale)
{
    IP_TextScale = scale;
}

/***************************************************************************
 *[@e
 *      Name:           IP_Blank
 *
 *      Usage:          Blank all channels of an image.
 *
 *      Synopsis:       void IP_Blank (IP_ImageId img)
 *
 *      Description:    All channels in image "img" are filled with zeroes.
 *                      The current drawing color, mode and mask have no
 *                      effect.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void IP_Blank (IP_ImageId img)
{
    IP_BlankRect (img, 0, 0, img->pimg.desc.width, img->pimg.desc.height);
}

void IP_BlankRect (IP_ImageId img, Int32 x1, Int32 y1, Int32 x2, Int32 y2)
{
    Int32   i, y;
    UInt8   *ubdest, *ubstop;
    Float32 *fdest, *fstop;
    Int32   xmin, ymin, xmax, ymax;  

    xmin = UT_MAX (0, x1);
    ymin = UT_MAX (0, y1);
    xmax = UT_MIN (x2, img->pimg.desc.width  - 1);
    ymax = UT_MIN (y2, img->pimg.desc.height - 1);
    for (i = 0; i < FF_NumImgChanTypes; ++i) {
        switch (img->pimg.channel[i]) {
            case FF_ImgFmtTypeNone:
                break;
            case FF_ImgFmtTypeUByte:
                for (y = ymin; y < ymax; ++y) {
                    ubdest = FF_ImgUByteRow (&img->pimg, i, y) + xmin;
                    ubstop = ubdest + (xmax - xmin +1);
                    while (ubdest < ubstop)
                        *(ubdest++) = 0;
                }
                break;
            case FF_ImgFmtTypeFloat:
                for (y = ymin; y < ymax; ++y) {
                    fdest = FF_ImgFloatRow (&img->pimg, i, y) + xmin;
                    fstop = fdest + (xmax - xmin +1);
                    while (fdest < fstop)
                        *(fdest++) = 0.0;
                }
                break;
            default:
                UT_ErrFatal ("IP_Blank", "unknown pixel data format");
        }
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetPixel
 *
 *      Usage:          Get the value of all channels of a pixel.
 *
 *      Synopsis:       UT_Bool IP_GetPixel
 *                              (const IP_ImageId img,
 *                               Int32 x, Int32 y,
 *                               Float32 clr[])
 *
 *      Description:    The value of all channels of pixel (x, y) in image
 *                      "img" is converted to FF_ImgFmtTypeFloat format and 
 *                      written to "clr".
 *
 *      Return value:   UT_True, if pixel is inside the boundaries of the image.
 *                      If (x, y) is outside the boundaries of the image the
 *                      content of "clr" is undefined and UT_False is returned.
 *
 *      See also:       IP_DrawPixel
 *
 ***************************************************************************/

UT_Bool IP_GetPixel (const IP_ImageId img, Int32 x, Int32 y, Float32 clr[])
{
    if ((x < 0) || (x >= img->pimg.desc.width) ||
        (y < 0) || (y >= img->pimg.desc.height)) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_invalid_pixel, x, y);
        return UT_False;
    }
    get_pixel (img, x, y, clr);
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_CopyRect
 *
 *      Usage:          Copy the pixels inside a rectangular source region in
 *                      an image, to a destination region in another image.
 *
 *      Synopsis:       void IP_CopyRect
 *                              (IP_ImageId srcimg, IP_ImageId destimg,
 *                              Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2,
 *                              Int32 xd1, Int32 yd1)
 *
 *      Description:    One corner of the source region is at position
 *                      (xs1, ys1) in image "srcimg". The diagonally opposite
 *                      corner of the source region is at position (xs2, ys2).
 *                      The corner of the destination region, which corresponds
 *                      to (xs1, ys1) in the source region, is at position
 *                      (xd1, yd1) in image "destimg".
 *
 *                      The current drawing color, mode and mask select which
 *                      channels in the destination region are affected, and
 *                      how the data from the source region are combined with
 *                      the original data in the destination region.
 *
 *                      Note: The source and destination regions may be
 *                      overlapping regions in the same image.
 *
 *      Return value:   None.
 *
 *      See also:       IP_WrapRect
 *
 ***************************************************************************/

void IP_CopyRect
        (IP_ImageId srcimg, IP_ImageId destimg,
         Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2, Int32 xd1, Int32 yd1)
{
    Int32       xs_start, xs_end, ys_start, ys_end, ys_inc,
                xoff, yoff, ys, xd2, yd2;

    /* Find the lower left and the upper right corner of the source
    and the destination region. */

    xd2 = xs2 - xs1 + xd1;
    yd2 = ys2 - ys1 + yd1;
    if (xs1 > xs2) {
        UT_SWAP (xs1, xs2, Int32);
        UT_SWAP (xd1, xd2, Int32);
    }
    if (ys1 > ys2) {
        UT_SWAP (ys1, ys2, Int32);
        UT_SWAP (yd1, yd2, Int32);
    }

    /* Clip the source and the destination region along the borders
    of the source and destination image. */

    xoff = xs1;
    if (xoff < 0) {
        xs1 -= xoff;
        xd1 -= xoff;
    }
    yoff = ys1;
    if (yoff < 0) {
        ys1 -= yoff;
        yd1 -= yoff;
    }
    xoff = xs2 - srcimg->pimg.desc.width + 1;
    if (xoff > 0) {
        xs2 -= xoff;
        xd2 -= xoff;
    }
    yoff = ys2 - srcimg->pimg.desc.height + 1;
    if (yoff > 0) {
        ys2 -= yoff;
        yd2 -= yoff;
    }
    xoff = xd1;
    if (xoff < 0) {
        xs1 -= xoff;
        xd1 -= xoff;
    }
    yoff = yd1;
    if (yoff < 0) {
        ys1 -= yoff;
        yd1 -= yoff;
    }
    xoff = xd2 - destimg->pimg.desc.width + 1;
    if (xoff > 0) {
        xs2 -= xoff;
        xd2 -= xoff;
    }
    yoff = yd2 - destimg->pimg.desc.height + 1;
    if (yoff > 0) {
        ys2 -= yoff;
        yd2 -= yoff;
    }
    if (xs1 > xs2 || ys1 > ys2)
        return;

    /* Find the correct start, end and increment values for the pixel copying
    loop below, so that no artefacts result from overlapping source and
    destination regions. */

    xoff = xd1 - xs1;
    if (xoff > 0) {
        xs_start = xs2;
        xs_end = xs1;
    } else {
        xs_start = xs1;
        xs_end = xs2;
    }
    yoff = yd1 - ys1;
    if (yoff > 0) {
        ys_start = ys2;
        ys_end = ys1;
        ys_inc = -1;
    } else {
        ys_start = ys1;
        ys_end = ys2;
        ys_inc = 1;
    }

    /* Copy the pixels. */

    for (ys = ys_start; ys != ys_end + ys_inc; ys += ys_inc)
        move_span (srcimg, destimg,
                   xs_start, xs_end, ys,
                   xs_start + xoff, ys + yoff);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_WrapRect
 *
 *      Usage:          Copy the pixels inside a rectangular source region in
 *                      an image, to a destination region in another image.
 *                      Wrap the rectangle at the image borders.
 *
 *      Synopsis:       void IP_WrapRect
 *                              (IP_ImageId srcimg, IP_ImageId destimg,
 *                              Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2,
 *                              Int32 sx, Int32 sy)
 *
 *      Description:    One corner of the source region is at position
 *                      (xs1, ys1) in image "srcimg". The diagonally opposite
 *                      corner of the source region is at position (xs2, ys2).
 *                      The corner of the destination region, which corresponds
 *                      to (xs1, ys1) in the source region, is at position
 *                      (xs1+sx, ys1+sy) in image "destimg".
 *
 *                      The current drawing color, mode and mask select which
 *                      channels in the destination region are affected, and
 *                      how the data from the source region are combined with
 *                      the original data in the destination region.
 *
 *                      Note: The source and destination regions may NOT be
 *                      overlapping regions in the same image.
 *
 *      Return value:   None.
 *
 *      See also:       IP_CopyRect
 *
 ***************************************************************************/

void IP_WrapRect
        (IP_ImageId srcimg, IP_ImageId destimg,
         Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2, Int32 sx, Int32 sy)
{
    Int32 w, h, xd1, yd1, xd2, yd2;

    w = srcimg->pimg.desc.width;
    h = srcimg->pimg.desc.height;

    if (xs1 > xs2)
        UT_SWAP (xs1, xs2, Int32);
    if (ys1 > ys2)
        UT_SWAP (ys1, ys2, Int32);

    xd1 = xs1 + sx;
    yd1 = ys1 + sy;
    xd2 = xs2 + sx;
    yd2 = ys2 + sy;

    IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1, yd1);

    if (xd2 > w - 1)
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1-w, yd1);

    if (yd2 > h - 1)
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1, yd1-h);

    if (xd1 < 0)
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1+w, yd1);

    if (yd1 < 0)
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1, yd1+h);

    if ((xd2 > w - 1) && (yd2 > h - 1))
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1-w, yd1-h);

    if ((xd2 > w - 1) && (yd1 < 0))
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1-w, yd1+h);

    if ((xd1 < 0) && (yd1 < 0))
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1+w, yd1+h);

    if ((xd1 < 0) && (yd2 > h - 1))
        IP_CopyRect (srcimg, destimg, xs1, ys1, xs2, ys2, xd1+w, yd1-h);

    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_CopyChannel
 *
 *      Usage:          Copy the pixel data of one channel in an image into
 *                      another channel.
 *
 *      Synopsis:       UT_Bool IP_CopyChannel
 *                              (IP_ImageId srcimg, IP_ImageId destimg,
 *                              FF_ImgChanType schn, FF_ImgChanType dchn)
 *
 *      Description:    The data from channel "schn" in image "srcimg"
 *                      are copied to channel "dchn" in image "destimg".
 *                      If width or height of the "srcimg" and "destimg"
 *                      differ, "srcimg" and "destimg" are aligned at the
 *                      lower left corner, and areas in "destimg", which
 *                      are not covered by "srcimg", are not affected by
 *                      IP_CopyChannel.
 *
 *                      The current drawing color, mode and mask have no
 *                      effect on IP_CopyChannel.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_CopyChannel
        (IP_ImageId srcimg, IP_ImageId destimg,
         FF_ImgChanType schn, FF_ImgChanType dchn)
{
    Int32       width, height, y;
    UInt8       *ubsrc, *ubdest, *ubstop;
    Float32     *fsrc, *fdest, *fstop;

    if (schn < 0 || schn >= FF_NumImgChanTypes) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan,
                      FF_ImgGetChanTypeName (schn));
        return UT_False;
    }
    if (dchn < 0 || dchn >= FF_NumImgChanTypes) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan,
                      FF_ImgGetChanTypeName (dchn));
        return UT_False;
    }
    width = UT_MIN (srcimg->pimg.desc.width, destimg->pimg.desc.width);
    height = UT_MIN (srcimg->pimg.desc.height, destimg->pimg.desc.height);
    for (y = 0; y < height; ++y) {
        switch (destimg->pimg.channel[dchn]) {
            case FF_ImgFmtTypeNone:
                break;
            case FF_ImgFmtTypeUByte:
                ubdest = FF_ImgUByteRow (&destimg->pimg, dchn, y);
                switch (srcimg->pimg.channel[schn]) {
                    case FF_ImgFmtTypeNone:
                        ubstop = ubdest + width;
                        while (ubdest < ubstop)
                            *(ubdest++) = 0;
                        break;
                    case FF_ImgFmtTypeUByte:
                        ubsrc = FF_ImgUByteRow (&srcimg->pimg, schn, y);
                        IP_CopyUByte2UByte (width, ubsrc, ubdest);
                        break;
                    case FF_ImgFmtTypeFloat:
                        fsrc = FF_ImgFloatRow (&srcimg->pimg, schn, y);
                        IP_CopyFloat2UByte (width, fsrc, ubdest);
                        break;
                }
                break;
            case FF_ImgFmtTypeFloat:
                fdest = FF_ImgFloatRow (&destimg->pimg, dchn, y);
                switch (srcimg->pimg.channel[schn]) {
                    case FF_ImgFmtTypeNone:
                        fstop = fdest + width;
                        while (fdest < fstop)
                            *(fdest++) = 0.0;
                        break;
                    case FF_ImgFmtTypeUByte:
                        ubsrc = FF_ImgUByteRow (&srcimg->pimg, schn, y);
                        IP_CopyUByte2Float (width, ubsrc, fdest);
                        break;
                    case FF_ImgFmtTypeFloat:
                        fsrc = FF_ImgFloatRow (&srcimg->pimg, schn, y);
                        IP_CopyFloat2Float (width, fsrc, fdest);
                        break;
                }
                break;
            default:
                UT_ErrFatal ("IP_CopyChannel", "invalid pixel format");
        }
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_CopyImage
 *
 *      Usage:          Copy the pixel data of one image into
 *                      another image.
 *
 *      Synopsis:       UT_Bool IP_CopyImage
 *                              (IP_ImageId srcimg, IP_ImageId destimg)
 *
 *      Description:    The data from all channels in image "srcimg"
 *                      are copied to image "destimg".
 *                      If width or height of the "srcimg" and "destimg"
 *                      differ, "srcimg" and "destimg" are aligned at the
 *                      lower left corner, and areas in "destimg", which
 *                      are not covered by "srcimg", are not affected by
 *                      IP_CopyImage.
 *
 *                      The current drawing color, mode and mask have no
 *                      effect on IP_CopyImage.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       IP_CopyChannel
 *
 ***************************************************************************/

UT_Bool IP_CopyImage (IP_ImageId srcimg, IP_ImageId destimg)
{
    Int32 chn;

    for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
        if (!IP_CopyChannel (srcimg, destimg, chn, chn)) {
            return UT_False;
        }
    }
    return UT_True;
}
