/* XBubble - rgba.c Copyright (C) 2002 Ivan Djelic 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 #include #include #include #include #include #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 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, int y1, int x2, int y2 ) { RgbaImage ri2; unsigned char *src; int row; int new_width; int new_height; int new_bytes_per_line; int bytes_per_line; if (( x1 < 0 )|| ( y1 < 0 )|| ( x2 >= ri->width )|| ( y2 >= ri->height )|| ( x1 > x2 )|| ( y1 > y2 )) fail( "invalid cropping parameters !" ); new_width = x2 - x1 + 1; new_height = y2 - y1 + 1; ri2 = new_rgba_image( new_width, new_height ); ri2->has_alpha = ri->has_alpha; ri2->hotx = ri->hotx - x1; ri2->hoty = ri->hoty - y1; bytes_per_line = ( ri->width << 2 ); new_bytes_per_line = ( new_width << 2 ); src = ri->data + (x1 << 2) + y1*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 ); }