/**************************************************************************
 *{@C
 *      Copyright:      1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Shear.c
 *
 *      Author:         Paul Obermeier
 *
 *	Description:	Functions to shear an image.
 *
 *      Additional documentation:
 *                      None.
 *
 *	Exported functions:
 *			IP_ShearVertical
 *			IP_ShearHorizontal
 *
 **************************************************************************/

#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"


/* A struct to pass parameters to the concurrently
   invoked image shearing functions */

typedef struct {
    IP_ImageId	img;		/* Image to be sheared */
    Int32	chn;		/* Channel to be sheared */
    void	*buf;		/* Buffer for intermediate results */
    Float32	m,		/* Slope */
		t;		/* Offset */
} shearparms;


/* Shift width for scaled integer arithmetic */

#define nshift		16


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

#define stacksize	(1 << 12)

/* Concurrent main loop to displace the
pixels in a UByte channel vertically. */

static void shear_vertical_UByte (void *ptr, Int32 n_conc, Int32 i_conc)
{
    shearparms	*parms;
    FF_ImgDesc	*pimg;
    Int32	chn, width, height,
		x, xmin, xmax,
		idy, y, ymin, ymax,
		v, vmin, vmax;
    Float32	m, t, dy;
    UInt32	*buf, a, b;
    UInt8	clr;

    /* Initialization */

    parms = ptr;
    pimg = &parms->img->pimg;
    chn = parms->chn;
    buf = parms->buf;
    buf += 1;
    m = parms->m;
    t = parms->t;
    width = pimg->desc.width;
    height = pimg->desc.height;
    n_conc = (n_conc == 0? 1: n_conc);
    xmin = (width * i_conc) / n_conc;
    xmax = (width * (i_conc + 1)) / n_conc;
    clr = IP_StateDrawColorUByte[chn];

    /* Main loop */

    for (x = xmin; x < xmax; ++x) {
	dy = m * x + t + 1.0;
	idy = (dy >= 0.0)? (Int32)dy: -(Int32)(1.0 - dy);
	a = (UInt32) ((dy - idy) * (1 << nshift));
	b = (1 << nshift) - a;

	ymin = -idy;
	if (ymin < 0)
	    ymin = 0;
	ymax = height - idy;
	if (ymax > height)
	    ymax = height;
	if (ymin > ymax) {
	    for (v = 0; v < height; ++v)
		*FF_ImgUBytePixel (pimg, chn, x, v) = clr;
	    continue;
	}

	for (y = ymin; y < ymax; ++y)
	    buf[y + idy] = *FF_ImgUBytePixel (pimg, chn, x, y);

	if (ymax < height)
	    buf[ymax + idy] = *FF_ImgUBytePixel (pimg, chn, x, ymax);
	else
	    buf[ymax + idy] = clr;

	if ((vmin = ymin + idy - 1) < 0)
	    vmin = 0;
	else
	    buf[vmin] = clr;
	if ((vmax = ymax + idy + 1) > height)
	    vmax = height;
	else
	    buf[vmax] = clr;

	for (v = 0; v < vmin; ++v)
	    *FF_ImgUBytePixel (pimg, chn, x, v) = clr;
	for (v = vmin; v < vmax; ++v)
	    *FF_ImgUBytePixel (pimg, chn, x, v) =
		(buf[v] * a + buf[v + 1] * b) >> nshift;
	for (v = vmax; v < height; ++v)
	    *FF_ImgUBytePixel (pimg, chn, x, v) = clr;
    }
    return;
}


/* Concurrent main loop to displace the
pixels in a UByte channel horizontally. */

static void shear_horizontal_UByte (void *ptr, Int32 n_conc, Int32 i_conc)
{
    shearparms	*parms;
    FF_ImgDesc	*pimg;
    Int32		chn, width, height,
		idx, xmin, xmax,
		y, ymin, ymax, umin, umax;
    Float32	m, t, dx;
    UInt32	*buf, a, b,
		*uidst, *uistop;
    const UInt32	*uisrc;
    UInt8	*ubdst, *ubstop;
    const UInt8	*ubsrc;
    UInt8	clr;

    /* Initialization */

    parms = ptr;
    pimg = &parms->img->pimg;
    chn = parms->chn;
    buf = parms->buf;
    buf += 1;
    m = parms->m;
    t = parms->t;
    width = pimg->desc.width;
    height = pimg->desc.height;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc;
    clr = IP_StateDrawColorUByte[chn];

    /* Main loop */

    for (y = ymin; y < ymax; ++y)
    {
	dx = m * y + t + 1.0;
	idx = (dx >= 0.0)? (Int32)dx: -(Int32)(1.0 - dx);
	a = (UInt32) ((dx - idx) * (1 << nshift));
	b = (1 << nshift) - a;

	xmin = -idx;
	if (xmin < 0)
	    xmin = 0;
	xmax = width - idx;
	if (xmax > width)
	    xmax = width;
	if (xmin > xmax)
	{
	    ubdst =  FF_ImgUBytePixel (pimg, chn, 0,     y);
	    ubstop = FF_ImgUBytePixel (pimg, chn, width, y);
	    while (ubdst < ubstop)
		*ubdst++ = clr;
	    continue;
	}

	ubsrc =  FF_ImgUBytePixel (pimg, chn, xmin, y);
	uidst =  buf + xmin + idx;
	uistop = buf + xmax + idx;
	while (uidst < uistop)
	    *uidst++ = *ubsrc++;

	if (xmax < width)
	    buf[xmax + idx] = *FF_ImgUBytePixel (pimg, chn, xmax, y);
	else
	    buf[xmax + idx] = clr;

	if ((umin = xmin + idx - 1) < 0)
	    umin = 0;
	else
	    buf[umin] = clr;
	if ((umax = xmax + idx + 1) > width)
	    umax = width;
	else
	    buf[umax] = clr;

	if (0 < umin)
	{
	    ubdst =  FF_ImgUBytePixel (pimg, chn, 0,    y);
	    ubstop = FF_ImgUBytePixel (pimg, chn, umin, y);
	    while (ubdst < ubstop)
		*ubdst++ = clr;
	}
	uisrc =  buf + umin;
	ubdst =  FF_ImgUBytePixel (pimg, chn, umin, y);
	ubstop = FF_ImgUBytePixel (pimg, chn, umax, y);
	while (ubdst < ubstop)
	{
	    *ubdst++ = (uisrc[0] *a + uisrc[1] * b) >> nshift;
	    ++uisrc;
	}
	if (umax < width)
	{
	    ubdst =  FF_ImgUBytePixel (pimg, chn, umax,  y);
	    ubstop = FF_ImgUBytePixel (pimg, chn, width, y);
	    while (ubdst < ubstop)
		*ubdst++ = clr;
	}
    }
    return;
}



/* Concurrent main loop to displace the
pixels in a Float32 channel vertically. */

static void shear_vertical_Float (void *ptr, Int32 n_conc, Int32 i_conc)
{
    shearparms	*parms;
    FF_ImgDesc	*pimg;
    Int32		chn, width, height,
		x, xmin, xmax,
		idy, y, ymin, ymax,
		v, vmin, vmax;
    Float32	m, t, dy, a, b;
    Float32	*buf, clr;

    /* Initialization */

    parms = ptr;
    pimg = &parms->img->pimg;
    chn = parms->chn;
    buf = parms->buf;
    buf += 1;
    m = parms->m;
    t = parms->t;
    width = pimg->desc.width;
    height = pimg->desc.height;
    n_conc = (n_conc == 0? 1: n_conc);
    xmin = (width * i_conc) / n_conc;
    xmax = (width * (i_conc + 1)) / n_conc;
    clr = IP_StateDrawColorFloat[chn];

    /* Main loop */

    for (x = xmin; x < xmax; ++x)
    {
	dy = m * x + t + 1.0;
	idy = (dy >= 0.0)? (Int32)dy: -(Int32)(1.0 - dy);
	a = dy - idy;
	b = 1.0 - a;

	ymin = -idy;
	if (ymin < 0)
	    ymin = 0;
	ymax = height - idy;
	if (ymax > height)
	    ymax = height;
	if (ymin > ymax)
	{
	    for (v = 0; v < height; ++v)
		*FF_ImgFloatPixel (pimg, chn, x, v) = clr;
	    continue;
	}

	for (y = ymin; y < ymax; ++y)
	    buf[y + idy] = *FF_ImgFloatPixel (pimg, chn, x, y);

	if (ymax < height)
	    buf[ymax + idy] = *FF_ImgFloatPixel (pimg, chn, x, ymax);
	else
	    buf[ymax + idy] = clr;

	if ((vmin = ymin + idy - 1) < 0)
	    vmin = 0;
	else
	    buf[vmin] = clr;
	if ((vmax = ymax + idy + 1) > height)
	    vmax = height;
	else
	    buf[vmax] = clr;

	for (v = 0; v < vmin; ++v)
	    *FF_ImgFloatPixel (pimg, chn, x, v) = clr;
	for (v = vmin; v < vmax; ++v)
	    *FF_ImgFloatPixel (pimg, chn, x, v) = buf[v] * a + buf[v + 1] * b;
	for (v = vmax; v < height; ++v)
	    *FF_ImgFloatPixel (pimg, chn, x, v) = clr;
    }
    return;
}



/* Concurrent main loop to displace the
pixels in a Float32 channel horizontally. */

static void shear_horizontal_Float (void *ptr, Int32 n_conc, Int32 i_conc)
{
    shearparms	*parms;
    FF_ImgDesc	*pimg;
    Int32		chn, width, height,
		idx, x, xmin, xmax,
		y, ymin, ymax,
		u, umin, umax;
    Float32	m, t, dx, a, b;
    Float32	*buf, clr;

    /* Initialization */

    parms = ptr;
    pimg = &parms->img->pimg;
    chn = parms->chn;
    buf = parms->buf;
    buf += 1;
    m = parms->m;
    t = parms->t;
    width = pimg->desc.width;
    height = pimg->desc.height;
    n_conc = (n_conc == 0? 1: n_conc);
    ymin = (height * i_conc) / n_conc;
    ymax = (height * (i_conc + 1)) / n_conc;
    clr = IP_StateDrawColorFloat[chn];

    /* Main loop */

    for (y = ymin; y < ymax; ++y)
    {
	dx = m * y + t + 1.0;
	idx = (dx >= 0.0)? (Int32)dx: -(Int32)(1.0 - dx);
	a = dx - idx;
	b = 1.0 - a;

	xmin = -idx;
	if (xmin < 0)
	    xmin = 0;
	xmax = width - idx;
	if (xmax > width)
	    xmax = width;
	if (xmin > xmax)
	{
	    for (u = 0; u < width; ++u)
		*FF_ImgFloatPixel (pimg, chn, u, y) = clr;
	    continue;
	}

	for (x = xmin; x < xmax; ++x)
	    buf[x + idx] = *FF_ImgFloatPixel (pimg, chn, x, y);

	if (xmax < width)
	    buf[xmax + idx] = *FF_ImgFloatPixel (pimg, chn, xmax, y);
	else
	    buf[xmax + idx] = clr;

	if ((umin = xmin + idx - 1) < 0)
	    umin = 0;
	else
	    buf[umin] = clr;
	if ((umax = xmax + idx + 1) > width)
	    umax = width;
	else
	    buf[umax] = clr;

	for (u = 0; u < umin; ++u)
	    *FF_ImgFloatPixel (pimg, chn, u, y) = clr;
	for (u = umin; u < umax; ++u)
	    *FF_ImgFloatPixel (pimg, chn, u, y) = buf[u] * a + buf[u + 1] * b;
	for (u = umax; u < width; ++u)
	    *FF_ImgFloatPixel (pimg, chn, u, y) = clr;
    }
    return;
}



/* Shear an image vertically; see IP_ShearVertical
   for an explanation of the parameters. */

static UT_Bool shear_vertical (IP_ImageId img, Float32 m, Float32 t)
{
    shearparms	parms;
    Float32	s;
    Int32	nbytes;

    /* Parameter check */

    s = 2.0 * (img->pimg.desc.width + img->pimg.desc.height);
    if (t < -s || t > s || m < -1.0 || m > 1.0) {
	UT_ErrSetNum (UT_ErrParamRange, str_shear_mt);
	return UT_False;
    }

    parms.img = img;
    parms.m = m;
    parms.t = t;

    /* Allocate memory for a temporary buffer used by the
    concurrent shearing routines to hold intermediate results. */

    nbytes = (img->pimg.desc.height + 2) *
	UT_MAX (sizeof (UInt32), sizeof (Float32));
    parms.buf = UT_MemTempAlloc (nbytes, "shear buffer vert " UT_MemIdString);
    if (!parms.buf) {
	return UT_False;
    }

    /* Displace the pixels, distributing the work
    load onto multiple concurrent threads. */

    for (parms.chn = 0; parms.chn < FF_NumImgChanTypes; ++parms.chn) {
	if (!IP_StateDrawMask[parms.chn]) {
	    continue;
	}
	switch (img->pimg.channel[parms.chn]) {
	    case FF_ImgFmtTypeNone: {
		break;
	    }
	    case FF_ImgFmtTypeUByte: {
		if (!UT_THREAD_EXEC (shear_vertical_UByte, &parms,
				     IP_StateNumThreads, stacksize)) {
		    return UT_False;
		}
		break;
	    }
	    case FF_ImgFmtTypeFloat: {
		if (!UT_THREAD_EXEC (shear_vertical_Float, &parms,
				     IP_StateNumThreads, stacksize)) {
		    return UT_False;
		}
		break;
	    }
	    default: {
		UT_ErrFatal ("shear_vertical", "invalid channel type");
	    }
	}
    }
    return UT_True;
}



/* Shear an image horizontally; see IP_ShearHorizontal
for an explanation of the parameters. */

static UT_Bool shear_horizontal (IP_ImageId img, Float32 m, Float32 t)
{
    shearparms	parms;
    Float32	s;
    Int32	nbytes;

    /* Parameter check */

    s = 2.0 * (img->pimg.desc.width + img->pimg.desc.height);
    if (t < -s || t > s || m < -1.0 || m > 1.0) {
	UT_ErrSetNum (UT_ErrParamRange, str_shear_mt);
	return UT_False;
    }

    parms.img = img;
    parms.m = m;
    parms.t = t;

    /* Allocate memory for a temporary buffer used by the
    concurrent shearing routines to hold intermediate results. */

    nbytes = (img->pimg.desc.width + 2) *
	UT_MAX (sizeof (UInt32), sizeof (Float32));
    parms.buf = UT_MemTempAlloc (nbytes, "shear buffer horz " UT_MemIdString);
    if (!parms.buf) {
	return UT_False;
    }

    /* Displace the pixels, distributing the work
    load onto multiple concurrent threads. */

    for (parms.chn = 0; parms.chn < FF_NumImgChanTypes; ++parms.chn) {
	if (!IP_StateDrawMask[parms.chn]) {
	    continue;
	}
	switch (img->pimg.channel[parms.chn]) {
	    case FF_ImgFmtTypeNone: {
		break;
	    }
	    case FF_ImgFmtTypeUByte: {
		if (!UT_THREAD_EXEC (shear_horizontal_UByte, &parms,
				     IP_StateNumThreads, stacksize)) {
		    return UT_False;
		}
		break;
	    }
	    case FF_ImgFmtTypeFloat: {
		if (!UT_THREAD_EXEC (shear_horizontal_Float, &parms,
				     IP_StateNumThreads, stacksize)) {
		    return UT_False;
		}
		break;
	    }
	    default: {
		UT_ErrFatal ("shear_horizontal", "invalid channel type");
	    }
	}
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		IP_ShearVertical
 *
 *	Usage:		Shear an image vertically.
 *
 *	Synopsis:	UT_Bool IP_ShearVertical
 *				(IP_ImageId img,
 *				 Float32 m,
 *				 Float32 t)
 *
 *	Description:	IP_ShearVertical shears all channels of image
 *			"img", for which the current drawing mask is UT_True,
 *			by displacing the pixels in "img" vertically:
 *			Pixel (x, y) is moved to position (x, y + x*m + t).
 *
 *			IP_ShearHorizontal shears image "img" by displacing
 *			the pixels horizontally:
 *			Pixel (x, y) is moved to position (x + y*m + t, y).
 *
 *			Both, IP_ShearVertical and IP_ShearHorizontal, fill
 *			regions in "img", which are not covered by the sheared
 *			image, with the current drawing color.
 *
 *			Notes:
 *
 *			- "M" must be in the range from -1.0 to 1.0; "t" must
 *			  be in the range from (-2.0 * s) to (2.0 * s), where
 *			  "s" is the sum of the width and height of the image,
 *			  in pixels.
 *
 *			- Images can be shifted with sub-pixel precision using
 *			  IP_ShearVertical and IP_ShearHorizontal.
 *
 *			- IP_ShearVertical and IP_ShearHorizontal can
 *			  be employed to to rotate images by small angles.
 *			  Rotations by larger angles result in undesirable
 *			  scaling of the image.  If an image with square
 *			  pixels is rotated by angle "phi", using shearing
 *			  operations, the image is scaled larger by a factor
 *			  "f", where
 *
 *			      f = sqrt (1.0 + square (tan (phi))).
 *
 *			- IP_ShearVertical and IP_ShearHorizontal can
 *			  be run concurrently on multiple processors; see
 *			  IP_SetNumThreads.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also: 	IP_ShearHorizontal
 *
 ***************************************************************************/

UT_Bool IP_ShearVertical (IP_ImageId img, Float32 m, Float32 t)
{
    UT_MemState	memstate;
    UT_Bool	success;

    memstate = UT_MemRemember ();
    success = shear_vertical (img, m, t);
    UT_MemRestore (memstate);
    return success;
}

/***************************************************************************
 *[@e
 *	Name:		IP_ShearHorizontal
 *
 *	Usage:		Shear an image horizontally.
 *
 *	Synopsis:	UT_Bool IP_ShearHorizontal
 *				(IP_ImageId img,
 *				 Float32 m,
 *				 Float32 t)
 *
 *	Description:	See IP_ShearVertical for detailed description. 
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	IP_ShearVertical
 *
 ***************************************************************************/

UT_Bool IP_ShearHorizontal (IP_ImageId img, Float32 m, Float32 t)
{
    UT_MemState	memstate;
    UT_Bool	success;

    memstate = UT_MemRemember ();
    success = shear_horizontal (img, m, t);
    UT_MemRestore (memstate);
    return success;
}
