/*
  XBubble - rgba.c

  Copyright (C) 2002  Ivan Djelic <ivan@savannah.gnu.org>
  
  This program 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 of the License, or
  (at your option) any later version.
  
  This program 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

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

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "setting.h"
#include "utils.h"
#include "loadpng.h"
#include "rgba.h"

extern Display *display;
extern Visual *visual;
extern Window root;
extern int screen;
extern int depth;

/* 
   precision of integer computations
   FRACBITS = 12 because 32 = 8 + 12 + 12 ( 1 byte << 12 twice )
*/
#define FRACBITS       ( 12 )
#define FRACONE        ( 1 << FRACBITS )
#define FRACBITSX2     ( 2*FRACBITS )
#define FRACMASK       ( FRACONE - 1 )
#define MASK_THRESHOLD ( 127 )

#define min(x,y) (((x) < (y))? (x):(y))
#define max(x,y) (((x) > (y))? (x):(y))

RgbaImage new_rgba_image( int width, int height ) {
  RgbaImage ri = (RgbaImage) xmalloc( sizeof(struct _RgbaImage) );
  ri->data = (unsigned char *) xmalloc( 4*width*height );
  ri->width = width;
  ri->height = height;
  return ri;
}

void delete_rgba_image( RgbaImage ri ) {
  free(ri->data);
  free(ri);
}

RgbaImage create_rgba_image_from_png_file( const char *filename ) {
  int width;
  int height;
  int has_alpha;
  unsigned char * data;
  RgbaImage ri;

  data = load_png_file( filename, &width, &height, &has_alpha );
  if ( data == NULL )
    fail( "cannot load PNG file %s.", filename );
  ri = (RgbaImage) xmalloc( sizeof(struct _RgbaImage) );
  ri->data = data;
  ri->width = width;
  ri->height = height;
  ri->has_alpha = has_alpha;
  return ri;
}

RgbaImage copy_rgba_image( RgbaImage ri ) {
  RgbaImage ri2 = new_rgba_image(ri->width, ri->height);
  memcpy(ri2->data, ri->data, 4*ri->width*ri->height);
  return ri2;
}

RgbaImage scale_rgba_image( RgbaImage ri, double scalex, double scaley ) {
  RgbaImage ri2;  
  long r;
  long g;
  long b;
  long a; /* 32 bits */
  unsigned char *dest;
  unsigned char *src;
  unsigned char *srcy;
  int *xsample;
  int *ysample;
  int new_width;
  int new_height; 
  int nb_samples;
  int bytes_per_line;
  int i;
  int x;
  int y;
  int xrange;
  int yrange;
  int rx;
  int ry;

  new_width = MAX((int) floor( scalex * ri->width ), 1);
  new_height = MAX((int) floor( scaley * ri->height ), 1);
  
  ri2 = new_rgba_image( new_width, new_height);
  ri2->has_alpha = ri->has_alpha;
  ri2->hotx = ri->hotx * new_width / ri->width;
  ri2->hoty = ri->hoty * new_height / ri->height;
  xsample = (int *) xmalloc( (new_width+1) * sizeof(int));
  ysample = (int *) xmalloc( (new_height+1) * sizeof(int));
  bytes_per_line = (ri->width << 2);
  
  /* precompute scale data */
  for ( i = 0; i <= new_width; i++ )
    xsample[i] = i*ri->width/new_width;
  for ( i = 0; i <= new_height; i++ )
    ysample[i] = i*ri->height/new_height * ri->width;
  dest = ri2->data;

  /* scan output image */
  for ( y = 0; y < new_height; y++ ) {
    yrange = ( ysample[y+1] - ysample[y] )/ri->width;
    for ( x = 0; x < new_width; x++) {
      xrange = xsample[x+1] - xsample[x];
      srcy = ri->data + (( ysample[y] + xsample[x] ) << 2 );
      /* average R,G,B,A values on sub-rectangle of source image */
      nb_samples = xrange * yrange;
      if ( nb_samples > 1 ) {
	r = 0;
	g = 0;
	b = 0;
	a = 0;
	for ( ry = 0; ry < yrange; ry++ ) {
	  src = srcy;
	  for ( rx = 0; rx < xrange; rx++ ) {
	    /* average R,G,B,A values */
	    r += *src++;
	    g += *src++;
	    b += *src++;
	    a += *src++;
	  }
	  srcy += bytes_per_line;
	}
	*dest++ = r/nb_samples;
	*dest++ = g/nb_samples;
	*dest++ = b/nb_samples;
	*dest++ = a/nb_samples; 
      }
      else {
	*((int *) dest) = *((int *) srcy);
	dest += 4;
      }
    }
  }
  /* cleanup */
  free( xsample );
  free( ysample );
  return ri2;
}

RgbaImage crop_rgba_image( RgbaImage ri, int x1_arg, int y1_arg, int x2_arg, int y2_arg ) {
  RgbaImage ri2;
  unsigned char *src;
  int row;
  int new_width;
  int new_height;
  int new_bytes_per_line;
  int bytes_per_line;
  
  if (( x1_arg < 0 )||
      ( y1_arg < 0 )||
      ( x2_arg >= ri->width )||
      ( y2_arg >= ri->height )||
      ( x1_arg > x2_arg )||
      ( y1_arg > y2_arg ))
    fail( "invalid cropping parameters !" );
  
  new_width = x2_arg - x1_arg + 1;
  new_height = y2_arg - y1_arg + 1;
  ri2 = new_rgba_image( new_width, new_height );
  ri2->has_alpha = ri->has_alpha; 
  ri2->hotx = ri->hotx - x1_arg;
  ri2->hoty = ri->hoty - y1_arg;
  bytes_per_line = ( ri->width << 2 );
  new_bytes_per_line = ( new_width << 2 );
  src = ri->data + (x1_arg << 2) + y1_arg*bytes_per_line;
  for ( row = 0; row < new_height; row++ )
    memcpy( ri2->data + row*new_bytes_per_line, 
	    src + row*bytes_per_line, new_bytes_per_line );
  return ri2;
}

RgbaImage rotate_rgba_image( RgbaImage ri, double angle, int autocrop ) {
  int i;
  int x;
  int y;
  int x1;
  int Y1;
  int x2;
  int y2; 
  long ucos;
  long usin;
  long uxmax;
  long uymax;
  long sxfrac;
  long syfrac;
  long u;
  long l;
  long ul;
  long ur;
  long ll;
  long lr;
  long sx;
  long sy;
  long kx;
  long ky;
  unsigned char *src;
  unsigned char *dest;
  unsigned char *data;
  unsigned char v;
  int bytes_per_line;
  int width = ri->width;
  RgbaImage ri2;
  RgbaImage ri3;

  bytes_per_line = ( width << 2 );
  ri2 = new_rgba_image( ri->width, ri->height );
  ri2->has_alpha = ri->has_alpha;
  ri2->hotx = ri->hotx; 
  ri2->hoty = ri->hoty;
  data = ri->data;
  dest = ri2->data;
  ucos = (int) ( cos(angle)*FRACONE );
  usin = (int) ( sin(angle)*FRACONE );
  uxmax = ( ri->width-1 ) << FRACBITS;
  uymax = ( ri->height-1 ) << FRACBITS;
  kx = ri->hotx*( FRACONE - ucos ) + ri->hoty*usin;
  ky = ri->hoty*( FRACONE - ucos ) - ri->hotx*usin;
  x1 = ri->width;
  Y1 = ri->height;
  x2 = -1;
  y2 = -1;
  
  for ( y = 0; y < ri->height; y++ ) {
    sx = kx - usin*y;
    sy = ky + ucos*y;
    for ( x = 0; x < ri->width; x++ ) {
      sx += ucos;
      sy += usin;
      /* check if source point is inside image */
      if (( sx < 0 )||( sy < 0 )||( sx >= uxmax )||( sy >= uymax )) {
	/* write black transparent pixel */
	*((int *) dest) = 0;
	dest += 4;
      }
      else {
	sxfrac = (sx & FRACMASK);
	syfrac = (sy & FRACMASK);
	src = data + (((sx >> FRACBITS) + (sy >> FRACBITS)*width)<<2);

	/* do not rotate invisible pixels */
	if ( src[3]|src[7]|src[bytes_per_line+3]|src[bytes_per_line+7] ) {
	  
	  /* interpolate R,G,B,A values */
	  for ( i = 0; i < 4; i++ ) {
	    ul = *src;
	    ur = src[4];
	    ll = src[bytes_per_line];
	    lr = src[bytes_per_line+4];
	    /* horizontal interpolation */
	    u = (ul << FRACBITS) + sxfrac*(ur-ul);
	    l = (ll << FRACBITS) + sxfrac*(lr-ll);
	    /* vertical interpolation */
	    v = ((u << FRACBITS) + syfrac*(l-u)) >> FRACBITSX2;
	    *dest++ = v;
	    src++;
	  }
	  /* compute clipping rectangle for autocropping */
	  if (( autocrop )&&( v > MASK_THRESHOLD )) {
	    x1 = min(x, x1);
	    Y1 = min(y, Y1);
	    x2 = max(x, x2);
	    y2 = max(y, y2);
	  }
	}
	else {
	  *((int *) dest) = 0;
	  dest += 4;
	}
      }
    }
  }
  /* autocrop if requested */
  if (( autocrop )&&( x2 >= x1 )&&( y2 >= Y1 )) {
    ri3 = crop_rgba_image( ri2, x1, Y1, x2, y2 );
    delete_rgba_image(ri2);
    return ri3;
  }
  else
    return ri2;
}

static int lsb( unsigned long mask ) {
  int k = 0;  
  if ( !mask )
    return 0;
  while ( !( mask & 0x01 )) {
    k++;
    mask >>= 1;
  }
  return k;
}

static int msb( unsigned long mask ) {
  int k = -1;
  if ( !mask )
    return 0;
  while ( mask ) {
    k++;
    mask >>= 1;
  }
  return k;
}

static void create_pixmap_from_image( XImage *xim, Pixmap *pixmap ) {
  GC gc;
  XGCValues gcv;
  gcv.foreground = 1;
  gcv.background = 0;
  *pixmap = XCreatePixmap( display, root, xim->width, xim->height, xim->depth);
  gc = XCreateGC( display, *pixmap, GCForeground | GCBackground, &gcv);
  XPutImage( display, *pixmap, gc, xim, 0, 0, 0, 0, xim->width, xim->height);
  XFreeGC( display, gc);
}

void create_pixmaps_from_rgba_image( RgbaImage ri, Pixmap *rgb, Pixmap *mask) {
  unsigned char *src;
  unsigned long r;
  unsigned long g;
  unsigned long b;
  unsigned long pixel;
  int x;
  int y;
  int bitmap_pad; 
  int lr_shift;
  int lg_shift;
  int lb_shift;
  int rr_shift;
  int rg_shift;
  int rb_shift;
  XImage *rgb_image;
  XImage *mask_image;

  if ( depth < 8 )
    fail( "your display depth must be >= 8." );
  
  bitmap_pad = ( depth > 16 )? 32 : (( depth > 8 )? 16 : 8 );
  /* create XImages */
  rgb_image = XCreateImage( display, visual, depth, ZPixmap, 0, NULL,
  			    ri->width, ri->height, bitmap_pad, 0);
  mask_image = XCreateImage( display, visual, 1, ZPixmap, 0, NULL,
			     ri->width, ri->height, 8, 0);
  if (( rgb_image == NULL )||( mask_image == NULL ))
    fail( "not enough memory for image allocation !" );
  
  /* allocate data using bytes_per_line field */
  rgb_image->data =  (char *) xmalloc( rgb_image->bytes_per_line*ri->height );
  mask_image->data = (char *) xmalloc( mask_image->bytes_per_line*ri->height );

  if ( depth > 8 ) {
    /* compute shifts for every channel */
    lr_shift = lsb( visual->red_mask );
    lg_shift = lsb( visual->green_mask );
    lb_shift = lsb( visual->blue_mask );
    rr_shift = 7 - msb( visual->red_mask ) + lr_shift;
    rg_shift = 7 - msb( visual->green_mask ) + lg_shift;
    rb_shift = 7 - msb( visual->blue_mask ) + lb_shift;
  }
  else { /* assume we have a RRRGGGBB 8-bits palette */
    lr_shift = 5;
    lg_shift = 2;
    lb_shift = 0;
    rr_shift = 5;
    rg_shift = 5;
    rb_shift = 6;
  }

  /* put pixels in images */
  src = ri->data;

  for ( y = 0; y < ri->height; y++ )
    for ( x = 0; x < ri->width; x++ ) {
      r = ( *src++ ) >> rr_shift;
      g = ( *src++ ) >> rg_shift;
      b = ( *src++ ) >> rb_shift;
      /* read alpha channel */
      if ( *src++ > MASK_THRESHOLD ) {
	pixel = ( r << lr_shift )|( g << lg_shift )|( b << lb_shift );
	XPutPixel( rgb_image, x, y, pixel);
	XPutPixel( mask_image, x, y, 1);
      }
      else
	XPutPixel( mask_image, x, y, 0);
    }
  create_pixmap_from_image( rgb_image, rgb );
  if ( mask != NULL )
    create_pixmap_from_image( mask_image, mask );
  /* cleanup */
  XDestroyImage(rgb_image);
  XDestroyImage(mask_image);
}

unsigned long get_pixel( int red, int green, int blue ) {
  int lr_shift;
  int lg_shift;
  int lb_shift;
  int rr_shift;
  int rg_shift;
  int rb_shift;

  if ( depth > 8 ) {
    /* compute shifts for every channel */
    lr_shift = lsb( visual->red_mask );
    lg_shift = lsb( visual->green_mask );
    lb_shift = lsb( visual->blue_mask );
    rr_shift = 7 - msb( visual->red_mask ) + lr_shift;
    rg_shift = 7 - msb( visual->green_mask ) + lg_shift;
    rb_shift = 7 - msb( visual->blue_mask ) + lb_shift;
  }
  else { /* assume we have a RRRGGGBB 8-bits palette */
    lr_shift = 5;
    lg_shift = 2;
    lb_shift = 0;
    rr_shift = 5;
    rg_shift = 5;
    rb_shift = 6;
  }
  return
    (( red >> rr_shift ) << lr_shift   )|
    (( green >> rg_shift ) << lg_shift )|
    (( blue >> rb_shift ) << lb_shift  );
}
