/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Flicker.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    A filter to reduce flickering on images which
 *                      will be displayed on interlaced video.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_Flicker
 *
 **************************************************************************/

#include <stdio.h>
#include <math.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"

/***************************************************************************
 *
 *      Macros and type definitions for concurrent processing
 *
 ***************************************************************************/

/* Parameters for the "conc_flicker" function (the image to be filtered) */

typedef struct {
    IP_ImageId img;
} flickerparms;

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

#define STACK_SIZE (1 << 12)

/***************************************************************************
 *
 *      Gamma-correction tables
 *
 ***************************************************************************/

static volatile Float32 tablegamma = -1.0;      /* Gamma factor for which
                                                   the tables below have been
                                                   set up */

static Float32          f_gamma_lin[GTABSIZE],  /* Table to convert floating
                                                   point pixel data from gamma-
                                                   corrected to linear */

                        f_lin_gamma[GTABSIZE];  /* Table to convert floating
                                                   point pixel data from linear
                                                   back to gamma-corrected */

static UInt8            ub_mix[256 * 256];      /* Table to lookup the gamma-
                                                   corrected mix of two UInt8
                                                   pixels for every possible
                                                   combination of values */

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

/* Store an approximation of function

    f(x, y) = pow ((pow (x, gamma) + pow (y, gamma)) * 0.5, 1.0 / gamma)

in table "ub_mix", where x, y and f(x, y) are expressed as UBytes,
and the range from 0 to 255 is interpreted as 0.0 to 1.0. */

static void make_ub_mix (Float32 gamma)
{
    Int32               x, y, t;
    Float64     gamma_1, powtab[256];

    for (x = 0; x < 256; ++x)
        powtab[x] = pow (x * (1.0 / 255.0), gamma) * 0.5;

    gamma_1 = 1.0 / gamma;

    for (y = 0; y < 256; ++y)
    {
        for (x = 0; x <= y; ++x)
        {
            t = (Int32)(pow (powtab[x] + powtab[y], gamma_1) * 255.0 + 0.5);
            ub_mix[x + (y << 8)] = ub_mix[y + (x << 8)] = UT_MAX (0, UT_MIN (t, 255));
        }
    }
    return;
}



/***************************************************************************
 *
 *      Functions which do the actual filtering
 *
 ***************************************************************************/

/* Flicker-filter UByte channel "chn" of image "img".  "X1", "y1",
"x2" and "y2" define the lower-left and upper-right corners of the
region to be processed. */

static void mix_UByte
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId img)
{
    Int32               y, n;
    UInt8       *scan1;
    const UInt8 *scan2, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y)
    {
        scan1 = FF_ImgUByteRow (&img->pimg, chn, y) + x1;
        scan2 = FF_ImgUByteRow (&img->pimg, chn, y + 1) + x1;
        stop = scan2 + n;
        while (scan2 < stop)
        {
            *scan1 = ub_mix[(UInt32)*scan1 + ((UInt32)*scan2 << 8)];
            ++scan1;
            ++scan2;
        }
    }
    return;
}



/* Flicker-filter Float32 channel "chn" of image "img". "X1", "y1",
"x2" and "y2" define the lower-left and upper-right corners of the
region to be processed.  */

static void mix_Float
        (Int32 x1, Int32 y1,
         Int32 x2, Int32 y2,
         FF_ImgChanType chn,
         IP_ImageId img)
{
    Int32         y, n;
    Float32       *scan1 = NULL, *scan2 = NULL, *dst;
    const Float32 *src, *stop;

    n = x2 - x1 + 1;
    for (y = y1; y <= y2; ++y) {
        scan1 = FF_ImgFloatRow (&img->pimg, chn, y) + x1;
        scan2 = FF_ImgFloatRow (&img->pimg, chn, y + 1) + x1;

        IP_GammaFloat2Float (n, scan2, f_gamma_lin, scan2);
        if (y == y1) {
            IP_GammaFloat2Float (n, scan1, f_gamma_lin, scan1);
        }

        src = scan2;
        dst = scan1;
        stop = scan2 + n;
        while (src < stop) {
            *dst = (*src + *dst) * 0.5;
            ++src;
            ++dst;
        }
        IP_GammaFloat2Float (n, scan1, f_lin_gamma, scan1);
    }
    IP_GammaFloat2Float (n, scan2, f_lin_gamma, scan2);
    return;
}



/* Concurrent main loop for function "flicker". */

static void conc_flicker (void *ptr, Int32 n_conc, Int32 i_conc)
{
    IP_ImageId  img;
    Int32               chn, xmin, xmax, ymax;

    /* Initialization */

    img = ((flickerparms *) ptr)->img;
    n_conc = (n_conc == 0? 1: n_conc);
    xmin = (img->pimg.desc.width * i_conc) / n_conc;
    xmax = (img->pimg.desc.width * (i_conc + 1)) / n_conc - 1;
    ymax =  img->pimg.desc.height - 2;

    /* Main loop */

    for (chn = 0; chn < FF_NumImgChanTypes; ++chn)
    {
        if (!IP_StateDrawMask[chn] || !img->pimg.channel[chn])
            continue;
        switch (img->pimg.channel[chn])
        {
        case FF_ImgFmtTypeUByte:
            mix_UByte (xmin, 0, xmax, ymax, chn, img);
            break;

        case FF_ImgFmtTypeFloat:
            mix_Float (xmin, 0, xmax, ymax, chn, img);
            break;

        default:
            UT_ErrFatal ("conc_flicker", "invalid pixel data format");
        }
    }
    return;
}



/* Prepare an image for display on a video monitor with interlaced scanning;
see the description of function IP_Flicker. */

static UT_Bool flicker (IP_ImageId img)
{
    flickerparms        parms;

    parms.img = img;

    if (img->pimg.desc.gamma != tablegamma)
    {
        if (!IP_FloatGammaTable (img->pimg.desc.gamma, f_gamma_lin) ||
            !IP_FloatGammaTable (1.0 / img->pimg.desc.gamma, f_lin_gamma))
            return UT_False;
        make_ub_mix (img->pimg.desc.gamma);
        tablegamma = img->pimg.desc.gamma;
    }
    return UT_THREAD_EXEC (conc_flicker,
                                &parms, IP_StateNumThreads, STACK_SIZE);
}



/***************************************************************************
 *[@e
 *      Name:           IP_Flicker
 *
 *      Usage:          Prepare an image for display on interlaced video.
 *
 *      Synopsis:       UT_Bool IP_Flicker (IP_ImageId img)
 *
 *      Description:    Image "img" is filtered so that flickering is reduced
 *                      when the image is displayed on a video monitor with
 *                      interlaced scanning: The pixel data in each scan line
 *                      are mixed with the data from the scan line immediately
 *                      above.
 *                      The current drawing mask selects which channels in
 *                      the image are filtered.
 *
 *                      Notes:
 *
 *                      - The filtered image will look slightly blurred,
 *                        and it will be shifted vertically by 0.5 pixels.
 *
 *                      - IP_Flicker can be run concurrently on multiple
 *                        processors; see IP_SetNumThreads.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_Flicker (IP_ImageId img)
{
    UT_MemState memstate;
    UT_Bool     success;

    memstate = UT_MemRemember ();
    success = flicker (img);
    UT_MemRestore (memstate);
    return success;
}
