/* XGBitmapImageRep.m NSBitmapImageRep for GNUstep GUI X/GPS Backend Copyright (C) 1996-1999 Free Software Foundation, Inc. Author: Adam Fedor Author: Scott Christley Date: Feb 1996 Author: Felipe A. Rodriguez Date: May 1998 Author: Richard Frith-Macdonald Date: Mar 1999 This file is part of the GNUstep GUI X/GPS Backend. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "gsc/gscolors.h" #include "xlib/XGPrivate.h" /* Macros that make it easier to convert between (r,g,b) <--> pixels. */ #define VARIABLES_DECLARATION \ unsigned char _rshift, _gshift, _bshift, _ashift; \ unsigned int _rmask, _gmask, _bmask, _amask; \ unsigned char _rwidth, _gwidth, _bwidth, _awidth #define InitRGBShiftsAndMasks(rs,rw,gs,gw,bs,bw,as,aw) \ do { \ _rshift = rs; \ _rmask = (1<> _rshift) & _rmask; \ g = (pixel >> _gshift) & _gmask; \ b = (pixel >> _bshift) & _bmask; \ } while(0) /* Note that RGBToPixel assumes that the r,g,b values are in the correct domain. If not, the value is nonsense. */ #define RGBToPixel(r,g,b, pixel) \ do { \ pixel = ((r) << _rshift) \ |((g) << _gshift) \ |((b) << _bshift); \ } while(0) /* Composite source image (pixmap) onto a destination image with alpha. Only works for op=Sover now Assumptions: - all images are valid. - srect is completely contained in the source images - srect put at the origin is contained in the destination image */ int _pixmap_combine_alpha(RContext *context, RXImage *source_im, RXImage *source_alpha, RXImage *dest_im, RXImage *dest_alpha, XRectangle srect, NSCompositingOperation op, XGDrawMechanism drawMechanism, float fraction) { unsigned long pixel; unsigned short oldAlpha = 0; unsigned long oldPixel = 0; unsigned short oldAr = 0; unsigned short oldAg = 0; unsigned short oldAb = 0; unsigned char oldBr = 0; unsigned char oldBg = 0; unsigned char oldBb = 0; if (drawMechanism == XGDM_FAST15 || drawMechanism == XGDM_FAST16 || drawMechanism == XGDM_FAST32 || drawMechanism == XGDM_FAST32_BGR) { VARIABLES_DECLARATION; unsigned row; switch (drawMechanism) { case XGDM_FAST15: InitRGBShiftsAndMasks(10,5,5,5,0,5,0,8); break; case XGDM_FAST16: InitRGBShiftsAndMasks(11,5,5,6,0,5,0,8); break; case XGDM_FAST32: InitRGBShiftsAndMasks(16,8,8,8,0,8,0,8); break; case XGDM_FAST32_BGR: InitRGBShiftsAndMasks(0,8,8,8,16,8,0,8); break; default: NSLog(@"Huh? Backend confused about XGDrawMechanism"); //Try something. With a bit of luck we see //which picture goes wrong. InitRGBShiftsAndMasks(11,5,5,6,0,5,0,8); } for (row = 0; row < srect.height; row++) { unsigned col; for (col = 0; col < srect.width; col++) { unsigned r, g, b, alpha; pixel = XGetPixel(source_im->image, col+srect.x, row+srect.y); PixelToRGB(pixel,r,g,b); pixel = 255; if (source_alpha) pixel = XGetPixel(source_alpha->image, col+srect.x, row+srect.y); if (fraction < 1) pixel *= fraction; alpha = (pixel >> _ashift) & _amask; if (alpha == 0) continue; // background unchanged. if (alpha != _amask) { // We really have something to mix! unsigned short ialpha = _amask - alpha; /* * Get the background pixel and convert to RGB. */ pixel = XGetPixel(dest_im->image, col, row); if (pixel != oldPixel) { oldPixel = pixel; PixelToRGB(pixel, oldBr,oldBg,oldBb); oldAlpha = 0; } if (alpha != oldAlpha) { oldAlpha = alpha; oldAr = ialpha * oldBr; oldAg = ialpha * oldBg; oldAb = ialpha * oldBb; } // mix in alpha to produce RGB out r = (oldAr + (r*alpha))/_amask; g = (oldAg + (g*alpha))/_amask; b = (oldAb + (b*alpha))/_amask; } RGBToPixel(r,g,b,pixel); XPutPixel(dest_im->image, col, row, pixel); if (dest_alpha) { unsigned short dalpha; unsigned long dpixel; /* Alpha gets mixed the same as all the other color components */ dpixel = XGetPixel(dest_alpha->image, col, row); dalpha = (dpixel >> _ashift) & _amask; dalpha = alpha + dalpha * (_amask - alpha)/_amask; XPutPixel(dest_alpha->image, col, row, dalpha << _ashift ); } } } } else { XColor c2; unsigned row; /* * This block of code should be totally portable as it uses the * 'official' X mechanism for converting from pixel values to * RGB color values - on the downside, it's very slow. */ pixel = (unsigned long)-1; // Never valid? c2.pixel = pixel; for (row = 0; row < srect.height; row++) { unsigned col; for (col = 0; col < srect.width; col++) { int r, g, b, alpha; XColor pcolor, acolor, c0, c1; pcolor.pixel = XGetPixel(source_im->image, col + srect.x, row + srect.y); XQueryColor(context->dpy, context->cmap, &pcolor); r = pcolor.red >> 8; g = pcolor.green >> 8; b = pcolor.blue >> 8; alpha = 255; if (source_alpha) { acolor.pixel = XGetPixel(source_alpha->image, col + srect.x, row + srect.y); XQueryColor(context->dpy, context->cmap, &acolor); alpha = acolor.red >> 8; } if (alpha == 0) continue; // background unchanged. if (fraction < 1) alpha *= fraction; if (alpha == 255) { c1 = pcolor; } else { unsigned short ialpha = 255 - alpha; /* * RGB color components in X are 16-bit values - * but our bitmap contains 8-bit values so we must * adjust our values up to 16-bit, which we can do * by increasing their alpha component by 256. */ alpha <<= 8; /* * Get the background pixel and convert to RGB if * we haven't already done the conversion earlier. */ c0.pixel = XGetPixel(dest_im->image, col, row); if (c0.pixel != oldPixel) { oldPixel = c0.pixel; XQueryColor(context->dpy, context->cmap, &c0); } // mix in alpha to produce RGB out c1.red = ((c0.red*ialpha) + (r*alpha))/255; c1.green = ((c0.green*ialpha) + (g*alpha))/255; c1.blue = ((c0.blue*ialpha) + (b*alpha))/255; } /* * Convert the X RGB value to a colormap entry if we * don't already have one. Then set the pixel. * NB. XAllocColor() will not necessarily return a * color with exactly the rgb components we gave it, so * we must record those components beforehand. */ if (c2.pixel == (unsigned long)-1 || c2.red != c1.red || c2.green != c1.green || c2.blue != c1.blue) { c2.red = c1.red; c2.green = c1.green; c2.blue = c1.blue; XAllocColor(context->dpy, context->cmap, &c1); c2.pixel = c1.pixel; } XPutPixel(dest_im->image, col, row, c1.pixel); if (dest_alpha) { XColor da; /* Alpha gets mixed the same as all the other color components */ da.pixel = XGetPixel(dest_alpha->image, col, row); XQueryColor(context->dpy, context->cmap, &da); da.red = acolor.red + da.red * (65536 - acolor.red)/65536; da.green = da.blue = da.red; XAllocColor(context->dpy, context->cmap, &da); XPutPixel(dest_alpha->image, col, row, da.pixel); } } } } return 0; } /* * The following structure holds the information necessary for unpacking * a bitmap data object. It holds an index into the raw data, precalculated * spans for magnification and minifaction, plus the buffer which holds * a complete line of colour to be output to the screen in RGBA form. */ struct _bitmap_decompose { unsigned char *plane[5]; int bit_off[5]; long image_w, image_h, screen_w, screen_h; int bps, spp, bpp, bpr; BOOL has_alpha, is_direct_packed, one_is_black; int cspace, pro_mul; unsigned char *r, *g, *b, *a; int cur_image_row, cur_screen_row; int first_vis_col, last_vis_col; int *row_starts, *row_ends, *col_starts, *col_ends; int *r_sum, *g_sum, *b_sum, *a_sum, *pix_count; }; /* * Here we extract a value a given number of bits wide from a bit * offset into a block of memory starting at "base". The bit numbering * is assumed to be such that a bit offset of zero and a width of 4 gives * the upper 4 bits of the first byte, *not* the lower 4 bits. We do allow * the value to cross a byte boundary, though it is unclear as to whether * this is strictly necessary for OpenStep tiffs. */ static int _get_bit_value(unsigned char *base, long msb_off, int bit_width) { long lsb_off, byte1, byte2; int shift, value; /* * Firstly we calculate the position of the msb and lsb in terms * of bit offsets and thus byte offsets. The shift is the number of * spare bits left in the byte containing the lsb */ lsb_off= msb_off+bit_width-1; byte1= msb_off/8; byte2= lsb_off/8; shift= 7-(lsb_off%8); /* * We now get the value from the byte array, possibly using two bytes if * the required set of bits crosses the byte boundary. This is then shifted * down to it's correct position and extraneous bits masked off before * being returned. */ value=base[byte2]; if(byte1!=byte2) value|= base[byte1]<<8; value >>= shift; return value & ((1<cur_screen_row >= img->screen_h) { NSLog(@"Tried to create too many screen rows"); return; } if(img->is_direct_packed) /* do direct copy, only limited formats supported */ { unsigned char *ptr = img->plane[0]; unsigned char *rptr = img->r; unsigned char *gptr = img->g; unsigned char *bptr = img->b; unsigned char *aptr = img->a; BOOL oib = img->one_is_black; BOOL ha = img->has_alpha; BOOL is_grey = (img->cspace == gray_colorspace); int i, fc = img->first_vis_col, lc = img->last_vis_col; /* do the offset from the right */ ptr += fc * ( (is_grey ? 1 : 3) + (ha ? 1 : 0) ); rptr += fc; gptr += fc; bptr += fc; aptr += fc; for(i = fc;i <= lc; i++) { *rptr = *ptr++; if(is_grey) { if(oib) *rptr = 255 - *rptr; *gptr = *bptr = *rptr; } else { *gptr = *ptr++; *bptr = *ptr++; } *aptr = ha ? *ptr++ : 255; /* opaque default */ rptr++; gptr++; bptr++; aptr++; } img->plane[0] += img->bpr; img->cur_image_row++; } else /* do the full averaging row creation */ { int tr, sc; int s_row = img->row_starts[img->cur_screen_row]; int e_row = img->row_ends[img->cur_screen_row]; BOOL zero_count = YES; /* local copies of stuff we use a lot to avoid indirect */ unsigned char **plane = img->plane; unsigned int *bit_off = img->bit_off; unsigned int *col_starts = img->col_starts; unsigned int *col_ends = img->col_ends; unsigned int *r_sum = img->r_sum; unsigned int *g_sum = img->g_sum; unsigned int *b_sum = img->b_sum; unsigned int *a_sum = img->a_sum; unsigned int *pix_count = img->pix_count; unsigned char *rptr = img->r; unsigned char *gptr = img->g; unsigned char *bptr = img->b; unsigned char *aptr = img->a; int spp = img->spp; int bpp = img->bpp; int bps = img->bps; int bpr = img->bpr; int pro_mul = img->pro_mul; int cspace = img->cspace; BOOL ha = img->has_alpha; BOOL oib = img->one_is_black; int fc = img->first_vis_col, lc = img->last_vis_col; /* loop for each required row */ zero_count = YES; for(tr = s_row; tr <= e_row; tr++) { /* move to the required image row */ while(img->cur_image_row < tr) { int p; for(p = 0; p < spp; p++) plane[p] += bpr; img->cur_image_row++; } /* for each screen pixel */ for(sc = fc; sc <= lc; sc++) { int s_col = col_starts[sc]; int e_col = col_ends[sc]; int tc; if(zero_count) { r_sum[sc] = 0; g_sum[sc] = 0; b_sum[sc] = 0; a_sum[sc] = 0; pix_count[sc] = 0; } /* for each image pixel */ for(tc = s_col; tc <= e_col; tc++) { unsigned char r, g, b, a; _get_image_pixel(tc, &r, &g, &b, &a, plane, bit_off, spp, bpp, bps, pro_mul, cspace, ha, oib); r_sum[sc] += r; g_sum[sc] += g; b_sum[sc] += b; a_sum[sc] += a; pix_count[sc]++; } } zero_count =NO; } /* * Build the line by averaging. As integer divide always * rounds down we can get the effect of adding 0.5 before * trucating by adding the value of the count right shifted * one bit, thus giving us the nearest value and therefore a * better approximation t the colour we hope. */ for(sc = fc; sc <= lc; sc++) { int count = pix_count[sc]; int half = count >> 1; rptr[sc] = (r_sum[sc] + half) / count; gptr[sc] = (g_sum[sc] + half) / count; bptr[sc] = (b_sum[sc] + half) / count; aptr[sc] = (a_sum[sc] + half) / count; } } img->cur_screen_row++; } /* * Set the ranges covered by a pixel within the image. Given the source * number of pixels and the destination number of pixels we calculate which * pixels in the source are more than 50% overlapped by each pixel in the * destination and record the start and end of the range. For mappings where * the source is being magnified this will only be a single pixel, for others * it may be one or more pixels, spaced evenly along the line. These are * the pixels which will then be averaged to make the best guess colour for * the destination pixel. As this is a slow process then a flag is passed in * which will cause the nearest pixel alorithm that is used for magnification * to be applied to minificationas well. The result looks rougher, but is much * faster and proprtional to the size of the output rather than the input * image. */ static void _set_ranges(int src_len, int dst_len, int *start_ptr, int *end_ptr, BOOL fast_min) { float dst_f = (float)dst_len; int d; if(fast_min || (src_len <= dst_len)) /* magnifying */ { float src_f = (float)src_len; for(d = 0; d < dst_len; d++) { int middle = (int)((((float)d + 0.5) * src_f) / dst_f); *start_ptr++ = middle; *end_ptr++ = middle; if(middle >= src_len) NSLog(@"Problem with magnification!"); } } else { int start = 0; for(d = 0; d < dst_len; d++) { int end_i = (int)(0.5 + (((d+1) * src_len) / dst_f)); if((end_i > src_len) || (end_i < 1)) NSLog(@"Problem with minification!"); *start_ptr++ = start; *end_ptr++ = end_i - 1; start = end_i; } } } /* * Combine the image data with the currentonscreen data and push the * result back to the screen. The screen handling code is almost identical * to the original code. The function assumes that the displayable rectangle * is always a subset of the complete screen rectangle which is the area * in pixels that would be covered by the entire image. This is used to * calculate the scaling required. */ int _bitmap_combine_alpha(RContext *context, unsigned char * data_planes[5], int width, int height, int bits_per_sample, int samples_per_pixel, int bits_per_pixel, int bytes_per_row, int colour_space, BOOL one_is_black, BOOL is_planar, BOOL has_alpha, BOOL fast_min, RXImage *dest_im, RXImage *dest_alpha, XRectangle srect, XRectangle drect, NSCompositingOperation op, XGDrawMechanism drawMechanism) { struct _bitmap_decompose img; XColor c0; XColor c1; int col_shift = drect.x - srect.x; int row_shift = drect.y - srect.y; /* Sanity check on colourspace and number of colours */ { int num_of_colours = samples_per_pixel - (has_alpha ? 1 : 0); switch(colour_space) { case hsb_colorspace: NSLog(@"HSB colourspace not supported for images"); return -1; case rgb_colorspace: if(num_of_colours != 3) { NSLog(@"Bad number of colour planes - d", num_of_colours); NSLog(@"RGB colourspace requires three planes excluding alpha"); return -1; } break; case cmyk_colorspace: if(num_of_colours != 4) { NSLog(@"Bad number of colour planes - %d", num_of_colours); NSLog(@"CMYK colourspace requires four planes excluding alpha"); return -1; } break; case gray_colorspace: if(num_of_colours != 1) { NSLog(@"Bad number of colour planes - %d", num_of_colours); NSLog(@"Gray colourspace requires one plane excluding alpha"); return -1; } break; default: NSLog(@"Unknown colourspace found"); return -1; } } /* bitmap decomposition structure */ img.bps = bits_per_sample; img.bpp = bits_per_pixel; img.spp = samples_per_pixel; img.bpr = bytes_per_row; img.image_w = width; img.image_h = height; img.screen_w = srect.width; img.screen_h = srect.height; img.has_alpha = has_alpha; img.one_is_black = one_is_black; img.cspace = colour_space; img.cur_image_row = 0; img.cur_screen_row = 0; /* * Promotion value, this is what the samples need to * be mutiplied by to get an 8 bit value. This is done * rather than shifting to fill in the lower bits properly. * The values for 6, 5 and 3 bits shouldbe floats by rights, * but the difference is negligble and for speed we want to use * integers. */ switch(bits_per_sample) { case 8: img.pro_mul = 1; break; case 7: img.pro_mul = 2; break; case 6: img.pro_mul = 4; /* should be 4.05 */ break; case 5: img.pro_mul = 8; /* should be 8.226 */ break; case 4: img.pro_mul = 17; break; case 3: img.pro_mul = 36; /* should be 36.43 */ break; case 2: img.pro_mul = 85; break; case 1: img.pro_mul = 255; break; default: NSLog(@"Bizzare number of bits per sample %d", bits_per_sample); return -1; } /* * Handle planar or meshed data. We hold an array of * base addresses and bit offsets for all formats. * Thus for meshed data the bases are the same with * increasing bit offsets, but for planar data the bases * are those of the planes, with the bit offset always being * zero. */ { int i; /* zero them */ for(i=0;i<5;i++) { img.plane[i] = NULL; img.bit_off[i] = 0; } /* set as appropriate */ if(is_planar) for(i=0;i>= (8 - _rwidth); g >>= (8 - _gwidth); b >>= (8 - _bwidth); if (has_alpha) { if (dest_alpha) { unsigned short dalpha; unsigned long dpixel; /* Alpha gets mixed the same as all the other color components */ dpixel = XGetPixel(dest_alpha->image, col, row); dalpha = (dpixel >> _ashift) & _amask; dalpha = alpha + dalpha * (_amask - alpha)/_amask; XPutPixel(dest_alpha->image, col, row, dalpha << _ashift); } if (alpha == 0) continue; // background unchanged. if (alpha != _amask) { unsigned short ialpha = _amask - alpha; /* * Get the background pixel and convert to RGB. */ pixel = XGetPixel(dest_im->image, col, row); if (pixel != oldPixel) { oldPixel = pixel; PixelToRGB(pixel,oldBr,oldBg,oldBb); oldAlpha = 0; } if (alpha != oldAlpha) { oldAlpha = alpha; oldAr = ialpha * oldBr; oldAg = ialpha * oldBg; oldAb = ialpha * oldBb; } // mix in alpha to produce RGB out r = (oldAr + (r * alpha)) / _amask; g = (oldAg + (g * alpha)) / _amask; b = (oldAb + (b * alpha)) / _amask; } } else { /* Not using alpha, but we still have to set it in the pixmap */ if (dest_alpha) XPutPixel(dest_alpha->image, col, row, _amask << _ashift); } RGBToPixel(r,g,b,pixel); XPutPixel(dest_im->image, col, row, pixel); } } } else { XColor c2, a2; unsigned row, oldAlpha = 65537; /* * This block of code should be totally portable as it uses the * 'official' X mechanism for converting from pixel values to * RGB color values - on the downside, it's very slow. */ pixel = (unsigned long)-1; // Never valid? c2.pixel = pixel; a2.pixel = pixel; for (row = 0; row < drect.height; row++) { unsigned col; unsigned char *rptr = img.r + col_shift; unsigned char *gptr = img.g + col_shift; unsigned char *bptr = img.b + col_shift; unsigned char *aptr = img.a + col_shift; _create_image_row(&img); for (col = 0; col < drect.width; col++) { unsigned short r = *rptr++; unsigned short g = *gptr++; unsigned short b = *bptr++; unsigned short alpha = *aptr++;; if (has_alpha) { if (alpha != oldAlpha) { oldAlpha = alpha; a2.red = alpha << 8; a2.green = alpha << 8; a2.blue = alpha << 8; XAllocColor(context->dpy, context->cmap, &a2); } if (dest_alpha) { XColor da; /* Alpha gets mixed the same as all the other color components */ da.pixel = XGetPixel(dest_alpha->image, col, row); XQueryColor(context->dpy, context->cmap, &da); da.red = a2.red + da.red * (65536 - a2.red)/65536; da.green = da.blue = da.red; XAllocColor(context->dpy, context->cmap, &da); XPutPixel(dest_alpha->image, col, row, da.pixel); } if (alpha == 0) continue; // background unchanged. if (alpha == 255) { c1.red = r << 8; c1.green = g << 8; c1.blue = b << 8; } else { unsigned short ialpha = 255 - alpha; /* * RGB color components in X are 16-bit values - * but our bitmap contains 8-bit values so we must * adjust our values up to 16-bit, which we can do * by increasing their alpha component by 256. */ alpha <<= 8; /* * Get the background pixel and convert to RGB if * we haven't already done the conversion earlier. */ c0.pixel = XGetPixel(dest_im->image, col, row); if (c0.pixel != pixel) { pixel = c0.pixel; XQueryColor(context->dpy, context->cmap, &c0); } // mix in alpha to produce RGB out c1.red = ((c0.red*ialpha) + (r*alpha))/255; c1.green = ((c0.green*ialpha) + (g*alpha))/255; c1.blue = ((c0.blue*ialpha) + (b*alpha))/255; } } else { /* * Simply convert our 8-bit RGB values to X-style * 16-bit values, then determine the X colormap * entry and set the pixel. */ c1.red = r << 8; c1.green = g << 8; c1.blue = b << 8; /* Not using alpha, but we still have to set it in the pixmap */ if (a2.pixel == pixel) { a2.red = 255 << 8; a2.green = 255 << 8; a2.blue = 255 << 8; XAllocColor(context->dpy, context->cmap, &a2); } if (dest_alpha) XPutPixel(dest_alpha->image, col, row, a2.pixel); } /* * Convert the X RGB value to a colormap entry if we * don't already have one. Then set the pixel. * NB. XAllocColor() will not necessarily return a * color with exactly the rgb components we gave it, so * we must record those components beforehand. */ if (c2.pixel == (unsigned long)-1 || c2.red != c1.red || c2.green != c1.green || c2.blue != c1.blue) { c2.red = c1.red; c2.green = c1.green; c2.blue = c1.blue; XAllocColor(context->dpy, context->cmap, &c1); c2.pixel = c1.pixel; } XPutPixel(dest_im->image, col, row, c1.pixel); } } } } /* free used memory */ free(img.r); free(img.g); free(img.b); free(img.a); if(!img.is_direct_packed) { free(img.row_starts); free(img.row_ends); free(img.col_starts); free(img.col_ends); free(img.r_sum); free(img.g_sum); free(img.b_sum); free(img.a_sum); free(img.pix_count); } return 0; }