/*
 * $Id: fbedge.c,v 1.5 2005/10/03 10:20:29 anholt Exp $
 *
 * Copyright © 2004 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include <string.h>

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include "fb.h"

#ifdef RENDER

#include "picturestr.h"
#include "mipict.h"
#include "renderedge.h"
#include "fbpict.h"

/*
 * 4 bit alpha
 */

#define N_BITS	4
#define rasterizeEdges	fbRasterizeEdges4

#if BITMAP_BIT_ORDER == LSBFirst
#define Shift4(o)	((o) << 2)
#else
#define Shift4(o)	((1-(o)) << 2)
#endif

#define Get4(x,o)	(((x) >> Shift4(o)) & 0xf)
#define Put4(x,o,v)	(((x) & ~(0xf << Shift4(o))) | (((v) & 0xf) << Shift4(o)))

#define DefineAlpha(line,x) \
    CARD8   *__ap = (CARD8 *) line + ((x) >> 1); \
    int	    __ao = (x) & 1

#define StepAlpha	((__ap += __ao), (__ao ^= 1))

#define AddAlpha(a) {						\
    CARD8   __o = *__ap;					\
    CARD8   __a = (a) + Get4(__o, __ao);			\
    *__ap = Put4 (__o, __ao, __a | (0 - ((__a) >> 4)));		\
}

#include "fbedgeimp.h"

#undef AddAlpha
#undef StepAlpha
#undef DefineAlpha
#undef rasterizeEdges
#undef N_BITS


/*
 * 1 bit alpha
 */

#define N_BITS 1
#define rasterizeEdges	fbRasterizeEdges1

#include "fbedgeimp.h"

#undef rasterizeEdges
#undef N_BITS

/*
 * 8 bit alpha
 */

static INLINE CARD8
clip255 (int x)
{
    if (x > 255) return 255;
    return x;
}

static INLINE void
add_saturate_8 (CARD8 *buf, int value, int length)
{
    while (length--)
    {
        *buf = clip255 (*buf + value);
        buf++;
    }
}

/*
 * We want to detect the case where we add the same value to a long
 * span of pixels.  The triangles on the end are filled in while we
 * count how many sub-pixel scanlines contribute to the middle section.
 *
 *                 +--------------------------+
 *  fill_height =|   \                      /
 *                     +------------------+
 *                      |================|
 *                   fill_start       fill_end
 */
static void
fbRasterizeEdges8 (FbBits	*buf,
		   int		width,
		   int		stride,
		   RenderEdge	*l,
		   RenderEdge	*r,
		   xFixed	t,
		   xFixed	b)
{
    xFixed  y = t;
    FbBits  *line;
    int fill_start = -1, fill_end = -1;
    int fill_size = 0;

    line = buf + xFixedToInt (y) * stride;

    for (;;)
    {
        CARD8 *ap = (CARD8 *) line;
	xFixed	lx, rx;
	int	lxi, rxi;
	
	/* clip X */
	lx = l->x;
	if (lx < 0)
	    lx = 0;
	rx = r->x;
	if (xFixedToInt (rx) >= width)
	    rx = IntToxFixed (width);
	
	/* Skip empty (or backwards) sections */
	if (rx > lx)
	{
            int lxs, rxs;

	    /* Find pixel bounds for span. */
	    lxi = xFixedToInt (lx);
	    rxi = xFixedToInt (rx);

            /* Sample coverage for edge pixels */
            lxs = RenderSamplesX (lx, 8);
            rxs = RenderSamplesX (rx, 8);

            /* Add coverage across row */
            if (lxi == rxi)
            {
                ap[lxi] = clip255 (ap[lxi] + rxs - lxs);
            }
            else
            {
                ap[lxi] = clip255 (ap[lxi] + N_X_FRAC(8) - lxs);

                /* Move forward so that lxi/rxi is the pixel span */
                lxi++;

                /* Don't bother trying to optimize the fill unless
                 * the span is longer than 4 pixels. */
                if (rxi - lxi > 4)
                {
                    if (fill_start < 0)
                    {
                        fill_start = lxi;
                        fill_end = rxi;
                        fill_size++;
                    }
                    else
                    {
                        if (lxi >= fill_end || rxi < fill_start)
                        {
                            /* We're beyond what we saved, just fill it */
                            add_saturate_8 (ap + fill_start,
                                            fill_size * N_X_FRAC(8),
                                            fill_end - fill_start);
                            fill_start = lxi;
                            fill_end = rxi;
                            fill_size = 1;
                        }
                        else
                        {
                            /* Update fill_start */
                            if (lxi > fill_start)
                            {
                                add_saturate_8 (ap + fill_start,
                                                fill_size * N_X_FRAC(8),
                                                lxi - fill_start);
                                fill_start = lxi;
                            }
                            else if (lxi < fill_start)
                            {
                                add_saturate_8 (ap + lxi, N_X_FRAC(8),
                                                fill_start - lxi);
                            }

                            /* Update fill_end */
                            if (rxi < fill_end)
                            {
                                add_saturate_8 (ap + rxi,
                                                fill_size * N_X_FRAC(8),
                                                fill_end - rxi);
                                fill_end = rxi;
                            }
                            else if (fill_end < rxi)
                            {
                                add_saturate_8 (ap + fill_end,
                                                N_X_FRAC(8),
                                                rxi - fill_end);
                            }
                            fill_size++;
                        }
                    }
                }
                else
                {
                    add_saturate_8 (ap + lxi, N_X_FRAC(8), rxi - lxi);
                }

                /* Do not add in a 0 alpha here. This check is
                 * necessary to avoid a buffer overrun, (when rx
                 * is exactly on a pixel boundary). */
                if (rxs)
                    ap[rxi] = clip255 (ap[rxi] + rxs);
            }
	}

	if (y == b) {
            /* We're done, make sure we clean up any remaining fill. */
            if (fill_start != fill_end) {
                if (fill_size == N_Y_FRAC(8))
                {
                    memset (ap + fill_start, 0xff, fill_end - fill_start);
                }
                else
                {
                    add_saturate_8 (ap + fill_start, fill_size * N_X_FRAC(8),
                                    fill_end - fill_start);
                }
            }
	    break;
        }

	if (xFixedFrac (y) != Y_FRAC_LAST(8))
	{
	    RenderEdgeStepSmall (l);
	    RenderEdgeStepSmall (r);
	    y += STEP_Y_SMALL(8);
	}
	else
	{
	    RenderEdgeStepBig (l);
	    RenderEdgeStepBig (r);
	    y += STEP_Y_BIG(8);
            if (fill_start != fill_end)
            {
                if (fill_size == N_Y_FRAC(8))
                {
                    memset (ap + fill_start, 0xff, fill_end - fill_start);
                }
                else
                {
                    add_saturate_8 (ap + fill_start, fill_size * N_X_FRAC(8),
                                    fill_end - fill_start);
                }
                fill_start = fill_end = -1;
                fill_size = 0;
            }
	    line += stride;
	}
    }
}

void
fbRasterizeEdges (FbBits	*buf,
		  int		bpp,
		  int		width,
		  int		stride,
		  RenderEdge	*l,
		  RenderEdge	*r,
		  xFixed	t,
		  xFixed	b)
{
    switch (bpp) {
    case 1:
	fbRasterizeEdges1 (buf, width, stride, l, r, t, b);
	break;
    case 4:
	fbRasterizeEdges4 (buf, width, stride, l, r, t, b);
	break;
    case 8:
	fbRasterizeEdges8 (buf, width, stride, l, r, t, b);
	break;
    }
}

#endif /* RENDER */
