/* This file is part of the Q programming system.

   The Q programming system is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   The Q programming system is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <limits.h>
#include "ggilib.h"

typedef struct {
  ggi_visual_t vis;
  void *abuf;
  unsigned char asz;
  unsigned short afg, abg;
  int stride, lines;
} visual_t;

/* calculate the size of a pixel pack */

static inline int pack_size(ggi_visual_t vis, int n)
{
  int bpp, r;
  ggi_mode m;
  if (ggiGetMode(vis, &m))
    return -1;
  bpp = GT_SIZE(m.graphtype);
  if (n > INT_MAX / bpp)
    return -1;
  r = ((bpp*n)%8>0)?1:0;
  return (bpp*n)/8+r;
}

/* clear clip area of alpha buffer */

int clear_alpha_buffer(void *_v)
{
  visual_t *v = (visual_t*)_v;
  int x1, y1, x2, y2;
  if (ggiGetGCClipping(v->vis, &x1, &y1, &x2, &y2)) return -1;
  if (v->asz == 1) {
    unsigned char *abuf = (unsigned char*)v->abuf;
    int i, y;
    for (i = y1*v->stride+x1, y = y1; y < y2; y++, i += v->stride)
      memset(abuf+i, v->abg, x2-x1);
  } else {
    unsigned short *abuf = (unsigned short*)v->abuf;
    int i, j, x, y;
    for (i = y1*v->stride+x1, y = y1; y < y2; y++, i += v->stride)
      for (j = i, x = x1; x < x2; x++, j++)
	abuf[j] = v->abg;
  }
  return 0;
}

/* set alpha values, with clipping */

int set_alpha_box(void *_v, int x, int y, int w, int h,
		  ggi_color *c)
{
  visual_t *v = (visual_t*)_v;
  int x1, y1, x2, y2;
  int i, j, k, xx, yy;
  if (ggiGetGCClipping(v->vis, &x1, &y1, &x2, &y2)) return -1;
  if (v->asz == 1) {
    unsigned char *abuf = (unsigned char*)v->abuf;
    /* FIXME: optimize me! */
    for (k = 0, i = y*v->stride+x, yy = y; yy < y+h; yy++, i += v->stride)
      for (j = i, xx = x; xx < x+w; xx++, j++, k++)
	if (xx >= x1 && xx < x2 && yy >= y1 && yy < y2)
	  abuf[j] = c[k].a/0x101;
  } else {
    unsigned short *abuf = (unsigned short*)v->abuf;
    /* FIXME: optimize me! */
    for (k = 0, i = y*v->stride+x, yy = y; yy < y+h; yy++, i += v->stride)
      for (j = i, xx = x; xx < x+w; xx++, j++, k++)
	if (xx >= x1 && xx < x2 && yy >= y1 && yy < y2)
	abuf[j] = c[k].a;
  }
  return 0;
}

/* same as above, but use current foreground color */

int draw_alpha_box(void *_v, int x, int y, int w, int h)
{
  visual_t *v = (visual_t*)_v;
  int x1, y1, x2, y2;
  int i, j, xx, yy;
  if (ggiGetGCClipping(v->vis, &x1, &y1, &x2, &y2)) return -1;
  if (v->asz == 1) {
    unsigned char *abuf = (unsigned char*)v->abuf;
    /* FIXME: optimize me! */
    for (i = y*v->stride+x, yy = y; yy < y+h; yy++, i += v->stride)
      for (j = i, xx = x; xx < x+w; xx++, j++)
	if (xx >= x1 && xx < x2 && yy >= y1 && yy < y2)
	  abuf[j] = v->afg;
  } else {
    unsigned short *abuf = (unsigned short*)v->abuf;
    /* FIXME: optimize me! */
    for (i = y*v->stride+x, yy = y; yy < y+h; yy++, i += v->stride)
      for (j = i, xx = x; xx < x+w; xx++, j++)
	if (xx >= x1 && xx < x2 && yy >= y1 && yy < y2)
	abuf[j] = v->afg;
  }
  return 0;
}

/* retrieve a rectangle of pixels from a visual, including alpha values */

int get_box(void *_v, int x, int y, int w, int h, ggi_color **c)
{
  visual_t *v = (visual_t*)_v;
  int n = w*h, bpp, stride, ret;
  int xc = x, yc = y, wc = w, hc = h;
  int i, j, k, xx, yy;
  unsigned char *buf;
  ggi_mode m;

  *c = NULL;
  /* FIXME: we don't support packed non-byte-aligned pixels for now */
  if (ggiGetMode(v->vis, &m) || (bpp = GT_SIZE(m.graphtype))%8 != 0)
    return 0;
  if (x >= v->stride || y >= v->lines || h <= 0 || w <= 0) return 0;
  if (w > INT_MAX/h) return -1;
  if (!(buf = malloc(pack_size(v->vis, n)))) return -1;
  if (n > INT_MAX/sizeof(ggi_color) || !(*c = malloc(n*sizeof(ggi_color)))) {
    free(buf);
    return -1;
  }
  /* we do our own clipping here, since GGI doesn't handle it :( */
  memset(buf, 0, n);
  memset(*c, 0, n*sizeof(ggi_color));
  if (xc < 0) {
    wc += xc;
    xc = 0;
  }
  if (xc+wc > v->stride)
    wc = v->stride - xc;
  if (yc < 0) {
    hc += yc;
    yc = 0;
  }
  if (yc+hc > v->lines)
    hc = v->lines - yc;
  if (hc <= 0 || wc <= 0) {
    free(buf); free(*c);
    return 0;
  }
  bpp /= 8;
  stride = w*bpp;
  if (xc == x && wc == w) {
    ret = ggiGetBox(v->vis, xc, yc, wc, hc, buf+(yc-y)*stride);
  } else {
    /* horizontal clipping, read the pixels line by line */
    unsigned char *bufp;
    int off = (xc-x)*bpp;
    for (bufp = buf+(yc-y)*stride, yy = yc; yy < yc+hc;
	 yy++, bufp += stride)
      if ((ret = ggiGetHLine(v->vis, xc, yy, wc, bufp+off)))
	break;
  }
  if (ret) {
    free(buf); free(*c);
    return 0;
  }
  if (ggiUnpackPixels(v->vis, buf, *c, n)) {
    free(buf); free(*c);
    return 0;
  }
  free(buf);
  /* get the alpha values from the alpha buffer if any */
  if (v->abuf) {
    if (v->asz == 1) {
      unsigned char *abuf = (unsigned char*)v->abuf;
      /* FIXME: optimize me! */
      for (i = y*v->stride+x, k = 0, yy = y; yy < y+h; yy++, i += v->stride)
	for (j = i, xx = x; xx < x+w; xx++, j++)
	  if (xx >= 0 && xx < v->stride && yy >= 0 && yy < v->lines)
	    (*c)[k++].a = abuf[j]*0x101;
	  else
	    (*c)[k++].a = 0xffff;
    } else {
      unsigned short *abuf = (unsigned short*)v->abuf;
      /* FIXME: optimize me! */
      for (i = y*v->stride+x, k = 0, yy = y; yy < y+h; yy++, i += v->stride)
	for (j = i, xx = x; xx < x+w; xx++, j++)
	  if (xx >= 0 && xx < v->stride && yy >= 0 && yy < v->lines)
	    (*c)[k++].a = abuf[j];
	  else
	    (*c)[k++].a = 0xffff;
    }
  } else {
    /* default alpha values */
    int i;
    for (i = 0; i < n; i++)
      (*c)[i].a = 0xffff;
  }
  return n;
}

/* perform alpha blending */

static int
blend_box(visual_t *v, int x, int y, int w, int h, ggi_color *c, ggi_color **d)
{
  int i, n = w*h;
  *d = c;
  if (!v->abuf) return n;
  if ((n = get_box(v, x, y, w, h, d)) <= 0 || !(*d))
    return n;
  for (i = 0; i < n; i++) {
    (*d)[i].r = (((unsigned long)c[i].a)*((unsigned long)c[i].r) +
      ((unsigned long)0xffff-c[i].a)*((unsigned long)(*d)[i].r))/0xffff;
    (*d)[i].g = (((unsigned long)c[i].a)*((unsigned long)c[i].g) +
      ((unsigned long)0xffff-c[i].a)*((unsigned long)(*d)[i].g))/0xffff;
    (*d)[i].b = (((unsigned long)c[i].a)*((unsigned long)c[i].b) +
      ((unsigned long)0xffff-c[i].a)*((unsigned long)(*d)[i].b))/0xffff;
    (*d)[i].a = (0xffffL*((unsigned long)c[i].a) +
      ((unsigned long)0xffff-c[i].a)*((unsigned long)(*d)[i].a))/0xffff;
  }
  return n;
}

/* same, for single source pixel */

static int
blend_box1(visual_t *v, int x, int y, int w, int h, ggi_color *c,
	   ggi_color **d)
{
  int i, n = w*h;
  *d = NULL;
  if (!v->abuf || c->a == 0xffff)
    return n;
  if ((n = get_box(v, x, y, w, h, d)) <= 0 || !(*d))
    return n;
  for (i = 0; i < n; i++) {
    (*d)[i].r = (((unsigned long)c->a)*((unsigned long)c->r) +
      ((unsigned long)0xffff-c->a)*((unsigned long)(*d)[i].r))/0xffff;
    (*d)[i].g = (((unsigned long)c->a)*((unsigned long)c->g) +
      ((unsigned long)0xffff-c->a)*((unsigned long)(*d)[i].g))/0xffff;
    (*d)[i].b = (((unsigned long)c->a)*((unsigned long)c->b) +
      ((unsigned long)0xffff-c->a)*((unsigned long)(*d)[i].b))/0xffff;
    (*d)[i].a = (0xffffL*((unsigned long)c->a) +
      ((unsigned long)0xffff-c->a)*((unsigned long)(*d)[i].a))/0xffff;
  }
  return n;
}

/* store a rectangle of pixels in a visual, with alpha blending */

int put_box(void *_v, int x, int y, int w, int h, ggi_color *c)
{
  visual_t *v = (visual_t*)_v;
  int n = w*h, ret;
  unsigned char *buf;
  ggi_color *d = c;
  if (h <= 0 || w <= 0) return 0;
  if (w > INT_MAX/h) return -1;
  if ((ret = blend_box(v, x, y, w, h, c, &d)) <= 0 || !d) return ret;
  if (!(buf = malloc(pack_size(v->vis, n)))) return -1;
  ret = ggiPackColors(v->vis, buf, d, n) ||
    ggiPutBox(v->vis, x, y, w, h, buf);
  free(buf);
  if (!ret && v->abuf)
    ret = set_alpha_box(v, x, y, w, h, d);
  if (d != c) free(d);
  if (ret)
    return 0;
  else
    return n;
}

/* same, with current foreground pixel */

int draw_box(void *_v, int x, int y, int w, int h)
{
  visual_t *v = (visual_t*)_v;
  int n = w*h, ret;
  unsigned char *buf;
  ggi_pixel pix;
  ggi_color c, *d = NULL;
  if (h <= 0 || w <= 0) return 0;
  if (ggiGetGCForeground(v->vis, &pix) ||
      ggiUnmapPixel(v->vis, pix, &c))
    return 0;
  if (v->abuf)
    c.a = (v->asz==1)?(v->afg*0x101):v->afg;
  else
    c.a = 0xffff;
  if (w > INT_MAX/h) return -1;
  if ((ret = blend_box1(v, x, y, w, h, &c, &d)) <= 0) return ret;
  if (d) {
    if (!(buf = malloc(pack_size(v->vis, n)))) return -1;
    ret = ggiPackColors(v->vis, buf, d, n) ||
      ggiPutBox(v->vis, x, y, w, h, buf);
    free(buf);
    if (!ret && v->abuf)
      ret = set_alpha_box(v, x, y, w, h, d);
    free(d);
  } else {
    ret = ggiDrawBox(v->vis, x, y, w, h);
    if (!ret && v->abuf)
      ret = draw_alpha_box(v, x, y, w, h);
  }
  if (ret)
    return 0;
  else
    return n;
}


syntax highlighted by Code2HTML, v. 0.9.1