/* * Image.c -- Image support routines. * * Authors : Patrick LECOANET * Creation date : Wed Dec 8 11:04:44 1999 */ /* * Copyright (c) 1999 - 2005 CENA, Patrick Lecoanet -- * * See the file "Copyright" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ #include "Types.h" #include "tkZinc.h" #include "Image.h" #include "WidgetInfo.h" #include "Geo.h" #include "Draw.h" #include "perfos.h" #include #include #ifdef GL #include #endif static const char rcsid[] = "$Id$"; static const char compile_id[] = "$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; static int images_inited = 0; static Tcl_HashTable images; #ifdef GL static Tcl_HashTable font_textures; #endif typedef struct _ClientStruct { void (*inv_proc)(void *cd); void *client_data; int refcount; } ClientStruct; typedef struct _ImageStruct { union { Pixmap pixmap; #ifdef GL GLuint texobj; #endif } i; Display *dpy; Screen *screen; struct _ImageBits *bits; /* Bookkeeping */ ZnBool for_gl; int refcount; ZnList clients; struct _ImageStruct *next; } ImageStruct, *Image; typedef struct _ImageBits { unsigned char *bpixels; /* Needed for bitmaps. Set to NULL if the image * is not a bitmap. */ int rowstride; #ifdef GL ZnReal t; /* Texture parameters for the image. */ ZnReal s; int t_width; /* Texture size used for this image. */ int t_height; unsigned char *t_bits; /* Can be NULL if texture is not used (no GL * rendering active on this image). */ #endif /* Bookeeping */ Display *dpy; /* The tkimage below comes from this display. */ Tcl_Interp *interp; /* The interp that created the tkimage below. */ Tk_Image tkimage; /* Keep this handle to be informed of changes */ Tk_PhotoHandle tkphoto; TkRegion valid_region; int width; int height; int depth; Tcl_HashEntry *hash; /* From this it is easy to get the image/bitmap * name. */ Image images; /* Linked list of widget/display dependant * specializations of this image. If NULL, the * image has no specialization and can be freed. */ } ImageBits; char *ZnNameOfImage(ZnImage image); #ifdef GL int To2Power(int a) { int result = 1; while (result < a) { result *= 2; } return result; } #endif /* ********************************************************************************** * * ZnGetImage -- * ********************************************************************************** */ static void InvalidateImage(ClientData client_data, int x, int y, int width, int height, int image_width, int image_height) { ImageBits *bits = (ImageBits *) client_data; Image this; int num_cs, count, i; ClientStruct *cs; char *image_name; /* printf("Invalidation, bits: %d, %d %d %d %d %d %d\n", client_data, x, y, width, height, image_width, image_height);*/ if (ZnImageIsBitmap(bits->images)) { /* This is a bitmap nothing to update * (This should not happen) */ return; } #ifdef GL if (bits->t_bits) { ZnFree(bits->t_bits); bits->t_bits = NULL; } #endif if (bits->valid_region) { TkDestroyRegion(bits->valid_region); bits->valid_region = NULL; } bits->width = image_width; bits->height = image_height; image_name = ZnNameOfImage(bits->images); /* * The photo pointer must be updated. It changes when creating an new image with * the same name as an old. The image is first deleted then re-instantiated. * As a side effect we also rely on it for telling if an image is a photo. */ bits->tkphoto = Tk_FindPhoto(bits->interp, image_name); count = 0; this = bits->images; while (this) { #ifdef GL if (this->for_gl) { if (this->i.texobj) { ZnGLContextEntry *ce; ce = ZnGLMakeCurrent(this->dpy, 0); glDeleteTextures(1, &this->i.texobj); ZnGLReleaseContext(ce); this->i.texobj = 0; } } else { #endif if (this->i.pixmap != None) { Tk_FreePixmap(this->dpy, this->i.pixmap); this->i.pixmap = None; } #ifdef GL } #endif num_cs = ZnListSize(this->clients); cs = ZnListArray(this->clients); for (i = 0; i < num_cs; i++, cs++) { if (cs->inv_proc) { (*cs->inv_proc)(cs->client_data); } } count += num_cs; this = this->next; } /*printf("Invalidate on image %s with %d clients\n", Tcl_GetHashKey(&images, bits->hash), count);*/ } ZnImage ZnGetImage(ZnWInfo *wi, Tk_Uid image_name, void (*inv_proc)(void *cd), void *client_data) { Tcl_HashEntry *entry; int new, num_cs, i; ImageBits *bits; ZnBool for_gl = wi->render>0; Image image; Tk_ImageType *type; ClientStruct cs, *cs_ptr; /*printf("ZnGetImage: %s\n", image_name);*/ if (!images_inited) { Tcl_InitHashTable(&images, TCL_STRING_KEYS); images_inited = 1; } image_name = Tk_GetUid(image_name); entry = Tcl_FindHashEntry(&images, image_name); if (entry != NULL) { /*printf("Image \"%s\" is in cache\n", image_name);*/ bits = (ImageBits *) Tcl_GetHashValue(entry); } else { /*printf("New image \"%s\"\n", image_name);*/ if (strcmp(image_name, "") == 0) { return ZnUnspecifiedImage; } bits = ZnMalloc(sizeof(ImageBits)); #ifdef GL bits->t_bits = NULL; #endif bits->images = NULL; bits->bpixels = NULL; bits->valid_region = NULL; bits->tkimage = NULL; bits->tkphoto = NULL; bits->interp = wi->interp; bits->dpy = wi->dpy; if (!Tk_GetImageMasterData(wi->interp, image_name, &type)) { /* * This doesn't seem to be a Tk image, try to load * a Tk bitmap. */ Pixmap pmap; XImage *mask; int x, y; unsigned char *line; pmap = Tk_GetBitmap(wi->interp, wi->win, image_name); if (pmap == None) { ZnWarning("unknown bitmap/image \""); goto im_error; } Tk_SizeOfBitmap(wi->dpy, pmap, &bits->width, &bits->height); mask = XGetImage(wi->dpy, pmap, 0, 0, (unsigned int) bits->width, (unsigned int) bits->height, 1L, ZPixmap); bits->depth = 1; bits->rowstride = mask->bytes_per_line; bits->bpixels = ZnMalloc((unsigned int) (bits->height * bits->rowstride)); memset(bits->bpixels, 0, (unsigned int) (bits->height * bits->rowstride)); line = bits->bpixels; for (y = 0; y < bits->height; y++) { for (x = 0; x < bits->width; x++) { if (XGetPixel(mask, x, y)) { line[x >> 3] |= 0x80 >> (x & 7); } } line += bits->rowstride; } XDestroyImage(mask); Tk_FreeBitmap(wi->dpy, pmap); } else if (strcmp(type->name, "photo") == 0) { /* Processing will yield an image photo */ bits->tkphoto = Tk_FindPhoto(wi->interp, image_name); Tk_PhotoGetSize(bits->tkphoto, &bits->width, &bits->height); if ((bits->width == 0) || (bits->height == 0)) { ZnWarning("bogus photo image \""); goto im_error; } bits->depth = Tk_Depth(wi->win); bits->tkimage = Tk_GetImage(wi->interp, wi->win, image_name, InvalidateImage, (ClientData) bits); } else { /* Other image types */ bits->depth = Tk_Depth(wi->win); bits->tkimage = Tk_GetImage(wi->interp, wi->win, image_name, InvalidateImage, (ClientData) bits); Tk_SizeOfImage(bits->tkimage, &bits->width, &bits->height); if ((bits->width == 0) || (bits->height == 0)) { ZnWarning("bogus "); ZnWarning(type->name); ZnWarning(" image \""); im_error: ZnWarning(image_name); ZnWarning("\"\n"); ZnFree(bits); return ZnUnspecifiedImage; } } entry = Tcl_CreateHashEntry(&images, image_name, &new); bits->hash = entry; Tcl_SetHashValue(entry, (ClientData) bits); } /* * Try to find an image instance that fits this widget/display. */ for (image = bits->images; image != NULL; image = image->next) { if (image->for_gl == for_gl) { if ((for_gl && (image->dpy == wi->dpy)) || (!for_gl && (image->screen == wi->screen))) { if (!ZnImageIsBitmap(image)) { cs_ptr = ZnListArray(image->clients); num_cs = ZnListSize(image->clients); for (i = 0; i < num_cs; i++, cs_ptr++) { if ((cs_ptr->inv_proc == inv_proc) && (cs_ptr->client_data == client_data)) { cs_ptr->refcount++; return image; } } /* Add a new client reference to call back. */ cs.inv_proc = inv_proc; cs.client_data = client_data; cs.refcount = 1; ZnListAdd(image->clients, &cs, ZnListTail); return image; } image->refcount++; return image; } } } /* * Create a new instance for this case. */ /*printf("new instance for \"%s\"\n", image_name);*/ image = ZnMalloc(sizeof(ImageStruct)); image->bits = bits; image->refcount = 0; image->for_gl = for_gl; image->dpy = wi->dpy; image->screen = wi->screen; if (!ZnImageIsBitmap(image)) { image->clients = ZnListNew(1, sizeof(ClientStruct)); cs.inv_proc = inv_proc; cs.client_data = client_data; cs.refcount = 1; ZnListAdd(image->clients, &cs, ZnListTail); } else { image->refcount++; } /* Init the real resource and let the client load it * on demand */ if (image->for_gl) { #ifdef GL image->i.texobj = 0; #endif } else { image->i.pixmap = None; /* image->i.pixmap = Tk_GetBitmap(wi->interp, wi->win, image_name); printf("pmap: %d\n", image->i.pixmap);*/ } image->next = bits->images; bits->images = image; return image; } /* ********************************************************************************** * * ZnGetImageByValue -- * ********************************************************************************** */ ZnImage ZnGetImageByValue(ZnImage image, void (*inv_proc)(void *cd), void *client_data) { Image this = (Image) image; ClientStruct cs, *cs_ptr; int i, num_cs; /*printf("ZnGetImageByValue: %s\n", ZnNameOfImage(image));*/ if (!ZnImageIsBitmap(image)) { cs_ptr = ZnListArray(this->clients); num_cs = ZnListSize(this->clients); for (i = 0; i < num_cs; i++, cs_ptr++) { if ((cs_ptr->inv_proc == inv_proc) && (cs_ptr->client_data == client_data)) { cs_ptr->refcount++; return image; } } cs.inv_proc = inv_proc; cs.client_data = client_data; cs.refcount = 1; ZnListAdd(this->clients, &cs, ZnListTail); } else { this->refcount++; } return image; } /* ********************************************************************************** * * ZnImageIsBitmap -- * ********************************************************************************** */ ZnBool ZnImageIsBitmap(ZnImage image) { return (((Image) image)->bits->bpixels != NULL); } /* ********************************************************************************** * * ZnFreeImage -- * ********************************************************************************** */ void ZnFreeImage(ZnImage image, void (*inv_proc)(void *cd), void *client_data) { Image prev, scan, this = ((Image) image); ImageBits *bits = this->bits; ClientStruct *cs_ptr; int i, num_cs, rm_image; /*printf("ZnFreeImage: %s\n", ZnNameOfImage(image));*/ /* * Search the instance in the list. */ for (prev=NULL, scan=bits->images; (scan!=NULL)&&(scan!=this); prev=scan, scan=scan->next); if (scan != this) { return; /* Not found ? */ } if (!ZnImageIsBitmap(image)) { cs_ptr = ZnListArray(this->clients); num_cs = ZnListSize(this->clients); for (i = 0; i < num_cs; i++, cs_ptr++) { if ((cs_ptr->inv_proc == inv_proc) && (cs_ptr->client_data == client_data)) { cs_ptr->refcount--; if (cs_ptr->refcount == 0) { ZnListDelete(this->clients, i); } break; } } rm_image = ZnListSize(this->clients)==0; } else { this->refcount--; rm_image = this->refcount==0; } if (!rm_image) { return; } /* * Unlink the deleted image instance. */ if (prev == NULL) { bits->images = this->next; } else { prev->next = this->next; } if (this->for_gl) { #ifdef GL if (this->i.texobj) { ZnGLContextEntry *ce; ce = ZnGLMakeCurrent(this->dpy, 0); /* printf("%d Liberation de la texture %d pour l'image %s\n", wi, this->i.texobj, ZnNameOfImage(image));*/ glDeleteTextures(1, &this->i.texobj); this->i.texobj = 0; ZnGLReleaseContext(ce); } #endif } else if (bits->tkimage) { /* * This is an image, we need to free the instances. */ if (this->i.pixmap != None) { Tk_FreePixmap(this->dpy, this->i.pixmap); } } else { /* * This is a bitmap ask Tk to free the resource. */ if (this->i.pixmap != None) { Tk_FreeBitmap(this->dpy, this->i.pixmap); } } ZnFree(this); /* * No clients for this image, it can be freed. */ if (bits->images == NULL) { /*printf("destruction complète de l'image %s\n", ZnNameOfImage(this));*/ #ifdef GL if (bits->t_bits) { ZnFree(bits->t_bits); } #endif if (bits->bpixels) { ZnFree(bits->bpixels); } if (bits->tkimage) { Tk_FreeImage(bits->tkimage); } if (bits->valid_region) { TkDestroyRegion(bits->valid_region); } Tcl_DeleteHashEntry(bits->hash); ZnFree(bits); } } /* ********************************************************************************** * * ZnNameOfImage -- * ********************************************************************************** */ char * ZnNameOfImage(ZnImage image) { return Tcl_GetHashKey(&images, ((Image) image)->bits->hash); } /* ********************************************************************************** * * ZnSizeOfImage -- * ********************************************************************************** */ void ZnSizeOfImage(ZnImage image, int *width, int *height) { Image this = (Image) image; *width = this->bits->width; *height = this->bits->height; } /* ********************************************************************************** * * ZnImagePixmap -- * ********************************************************************************** */ Pixmap ZnImagePixmap(ZnImage image, Tk_Window win) { Image this = (Image) image; ImageBits *bits = this->bits; /*printf("ZnImagePixmap: %s\n", ZnNameOfImage(image));*/ if (this->for_gl) { fprintf(stderr, "Bogus use of an image, it was created for GL and used in an X11 context\n"); return None; } if (this->i.pixmap == None) { if (ZnImageIsBitmap(image)) { this->i.pixmap = Tk_GetBitmap(bits->interp, win, Tk_GetUid(ZnNameOfImage(image))); } else { Tk_Image tkimage; /* * If the original image was created on the same display * as the required display, we can get the pixmap from it. * On the other hand we need first to obtain an image * instance on the right display. */ if (bits->dpy == this->dpy) { tkimage = bits->tkimage; } else { /* Create a temporary tkimage to draw the pixmap. */ tkimage = Tk_GetImage(bits->interp, win, ZnNameOfImage(image), NULL, NULL); } this->i.pixmap = Tk_GetPixmap(this->dpy, Tk_WindowId(win), bits->width, bits->height, bits->depth); Tk_RedrawImage(tkimage, 0, 0, bits->width, bits->height, this->i.pixmap, 0, 0); if (tkimage != bits->tkimage) { Tk_FreeImage(tkimage); } } } return this->i.pixmap; } /* ********************************************************************************** * * ZnPointInImage -- * * Return whether the given point is inside the image. * ********************************************************************************** */ ZnBool ZnPointInImage(ZnImage image, int x, int y) { if (ZnImageIsBitmap(image)) { ImageBits *bits = ((Image) image)->bits; if ((x < 0) || (y < 0) || (x >= bits->width) || (y >= bits->height)) { return False; } return ZnGetBitmapPixel(bits->bpixels, bits->rowstride, x, y); } else { return ZnPointInRegion(ZnImageRegion(image), x, y); } } /* ********************************************************************************** * * ZnImageRegion -- * * Only defined for Tk images (including Tk images defined from bitmaps). * ********************************************************************************** */ static void BuildImageRegion(Display *dpy, ImageBits *bits) { Pixmap pmap; int x, y, end; GC gc; XImage *im1, *im2; XRectangle rect; /*printf("BuildImageRegion: %s\n", ZnNameOfImage(bits->images));*/ pmap = Tk_GetPixmap(dpy, DefaultRootWindow(dpy), bits->width, bits->height, bits->depth); gc = XCreateGC(dpy, pmap, 0, NULL); XSetForeground(dpy, gc, 0); XFillRectangle(dpy, pmap, gc, 0, 0, bits->width, bits->height); Tk_RedrawImage(bits->tkimage, 0, 0, bits->width, bits->height, pmap, 0, 0); im1 = XGetImage(dpy, pmap, 0, 0, bits->width, bits->height, ~0L, ZPixmap); XSetForeground(dpy, gc, 1); XFillRectangle(dpy, pmap, gc, 0, 0, bits->width, bits->height); Tk_RedrawImage(bits->tkimage, 0, 0, bits->width, bits->height, pmap, 0, 0); im2 = XGetImage(dpy, pmap, 0, 0, bits->width, bits->height, ~0L, ZPixmap); Tk_FreePixmap(dpy, pmap); XFreeGC(dpy, gc); bits->valid_region = TkCreateRegion(); for (y = 0; y < bits->height; y++) { x = 0; while (x < bits->width) { while ((x < bits->width) && (XGetPixel(im1, x, y) != XGetPixel(im2, x, y))) { /* Search the first non-transparent pixel */ x++; } end = x; while ((end < bits->width) && (XGetPixel(im1, end, y) == XGetPixel(im2, end, y))) { /* Search the first transparent pixel */ end++; } if (end > x) { rect.x = x; rect.y = y; rect.width = end - x; rect.height = 1; TkUnionRectWithRegion(&rect, bits->valid_region, bits->valid_region); } x = end; } } XDestroyImage(im1); XDestroyImage(im2); } TkRegion ZnImageRegion(ZnImage image) { if (ZnImageIsBitmap(image)) { return NULL; } else { Image this = (Image) image; ImageBits *bits = this->bits; #ifdef PTK if (!bits->valid_region) { BuildImageRegion(this->dpy, bits); } return bits->valid_region; #else if (bits->tkphoto) { return TkPhotoGetValidRegion(bits->tkphoto); } else { if (!bits->valid_region) { BuildImageRegion(this->dpy, bits); } return bits->valid_region; } #endif } } Tk_Image ZnImageTkImage(ZnImage image) { return ((Image) image)->bits->tkimage; } Tk_PhotoHandle ZnImageTkPhoto(ZnImage image) { return ((Image) image)->bits->tkphoto; } /* ********************************************************************************** * * ZnImageTex -- * ********************************************************************************** */ #ifdef GL /* * Working only for 16 bits displays with 5r6g5b mask, * and 24/32 bits displays. Byte ordering ok on Intel * plateform only. */ static void From5r6g5b(unsigned char *data, int width, int height, int bytes_per_line, unsigned char *t_bits, int t_width, int t_height, TkRegion valid_region) { int x, y; int rowstride = t_width * 4; unsigned char *obptr; unsigned char *bptr, *bp2; unsigned char alpha; unsigned short temp; bptr = t_bits; for (y = 0; y < height; y++) { bp2 = bptr; obptr = data; for (x = 0; x < width; x++) { /* * Configure the alpha value. */ alpha = ZnPointInRegion(valid_region, x, y) ? 255 : 0; /* * Dispatch the 3 color components. */ temp = ((unsigned short *)obptr)[0]; *bp2 = (temp >> 8) & 0xf8; /* r */ bp2++; *bp2 = (temp >> 3) & 0xfc; /* v */ bp2++; *bp2 = (temp << 3); /* b */ bp2++; *bp2 = alpha; bp2++; obptr += 2; } for (x = width; x < t_width; x++) { *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; } bptr += rowstride; data += bytes_per_line; } for (y = height; y < t_height; y++) { memset(bptr, 0, rowstride); bptr += rowstride; } } static void From8r8g8b(unsigned char *data, int width, int height, int bytes_per_line, unsigned char *t_bits, int t_width, int t_height, TkRegion valid_region) { int x, y; int rowstride = t_width * 4; unsigned char *obptr; unsigned char *bptr, *bp2; unsigned char alpha; bptr = t_bits; for (y = 0; y < height; y++) { bp2 = bptr; obptr = data; for (x = 0; x < width; x++) { /* * Configure the alpha value. */ alpha = ZnPointInRegion(valid_region, x, y) ? 255 : 0; /* * Dispatch the 3 color components. * Be careful the Red and Blue are swapped it works on an Intel * plateform but may need some more tests to be fully generic. */ *bp2++ = obptr[2]; /* r */ *bp2++ = obptr[1]; /* v */ *bp2++ = obptr[0]; /* b */ obptr += 4; *bp2++ = alpha; } for (x = width; x < t_width; x++) { *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; } bptr += rowstride; data += bytes_per_line; } for (y = height; y < t_height; y++) { memset(bptr, 0, rowstride); bptr += rowstride; } } static void GatherImageTexels(Display *dpy, ImageBits *bits) { Pixmap pmap; XImage *im; TkRegion valid_region; int t_size; /*printf("GatherImageTexels: %s\n", ZnNameOfImage(bits->images));*/ valid_region = ZnImageRegion(bits->images); t_size = bits->t_width * 4 * bits->t_height; bits->t_bits = ZnMalloc(t_size); pmap = Tk_GetPixmap(dpy, DefaultRootWindow(dpy), bits->width, bits->height, bits->depth); Tk_RedrawImage(bits->tkimage, 0, 0, bits->width, bits->height, pmap, 0, 0); im = XGetImage(dpy, pmap, 0, 0, bits->width, bits->height, ~0L, ZPixmap); Tk_FreePixmap(dpy, pmap); if (bits->depth == 16) { From5r6g5b(im->data, bits->width, bits->height, im->bytes_per_line, bits->t_bits, bits->t_width, bits->t_height, valid_region); } else if ((bits->depth == 24) || (bits->depth == 32)) { From8r8g8b(im->data, bits->width, bits->height, im->bytes_per_line, bits->t_bits, bits->t_width, bits->t_height, valid_region); } XDestroyImage(im); } GLuint ZnImageTex(ZnImage image, ZnReal *t, ZnReal *s) { Image this = (Image) image; ImageBits *bits = this->bits; ZnBool is_bmap = ZnImageIsBitmap(image); unsigned int t_size, width, height; if (!this->for_gl) { fprintf(stderr, "Bogus use of an image, it was created for X11 and used in a GL context\n"); return 0; } ZnSizeOfImage(image, &width, &height); if (!bits->t_bits) { /*printf("chargement texture pour image %s\n", ZnNameOfImage(this));*/ bits->t_width = To2Power((int) width); bits->t_height = To2Power((int) height); bits->s = width / (ZnReal) bits->t_width; bits->t = height / (ZnReal) bits->t_height; /* * This is a bitmap: use the pixels stored in bpixels. */ if (is_bmap) { unsigned int i, j; unsigned char *ostart, *dstart, *d, *o; t_size = bits->t_width * bits->t_height; bits->t_bits = ZnMalloc(t_size); memset(bits->t_bits, 0, t_size); ostart = bits->bpixels; dstart = bits->t_bits; for (i = 0; i < height; i++) { d = dstart; o = ostart; for (j = 0; j < width; j++) { *d++ = ZnGetBitmapPixel(bits->bpixels, bits->rowstride, j, i) ? 255 : 0; } ostart += bits->rowstride; dstart += bits->t_width; } } /* * This is a photo: use the photo API, it is simple. */ else if (bits->tkphoto) { unsigned int x, y, t_stride; unsigned char *obptr, *bptr, *bp2, *pixels; int green_off, blue_off, alpha_off; Tk_PhotoImageBlock block; t_stride = bits->t_width * 4; t_size = t_stride * bits->t_height; /*printf("t_width: %d(%d), t_height: %d(%d)\n", bits->t_width, width, bits->t_height, height);*/ bits->t_bits = ZnMalloc(t_size); Tk_PhotoGetImage(bits->tkphoto, &block); green_off = block.offset[1] - block.offset[0]; blue_off = block.offset[2] - block.offset[0]; #ifdef PTK alpha_off = 3; #else alpha_off = block.offset[3] - block.offset[0]; #endif /*printf("width %d, height: %d redoff: %d, greenoff: %d, blueoff: %d, alphaoff: %d\n", block.width, block.height, block.offset[0], green_off, blue_off, alpha_off);*/ pixels = block.pixelPtr; bptr = bits->t_bits; for (y = 0; y < height; y++) { bp2 = bptr; obptr = pixels; for (x = 0; x < width; x++) { *bp2++ = obptr[0]; /* r */ *bp2++ = obptr[green_off]; /* g */ *bp2++ = obptr[blue_off]; /* b */ *bp2++ = obptr[alpha_off]; /* alpha */ obptr += block.pixelSize; } /*for (x = width; x < t_width; x++) { *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; *bp2 = 0; bp2++; }*/ bptr += t_stride; pixels += block.pitch; } /*for (y = height; y < t_height; y++) { memset(bptr, 0, t_stride); bptr += t_stride; }*/ } /* * This is another image format (not a photo): try to * guess the pixels and the transparency (on or off) * from a locally drawn pixmap. */ else { GatherImageTexels(bits->dpy, bits); } } if (!this->i.texobj) { glGenTextures(1, &this->i.texobj); /*printf("%d creation texture %d pour image %s\n", bits, this->i.texobj, ZnNameOfImage(this));*/ glBindTexture(GL_TEXTURE_2D, this->i.texobj); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glGetError(); if (is_bmap) { glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY4, this->bits->t_width, this->bits->t_height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, this->bits->t_bits); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this->bits->t_width, this->bits->t_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, this->bits->t_bits); } if (glGetError() != GL_NO_ERROR) { ZnWarning("Can't allocate the texture for image "); ZnWarning(ZnNameOfImage(image)); ZnWarning("\n"); } glBindTexture(GL_TEXTURE_2D, 0); } *t = this->bits->t; *s = this->bits->s; return this->i.texobj; } #endif #ifdef GL #define MAX_GLYPHS_PER_GRAB 256 typedef struct _TexFontInfo { struct _TexFont *txf; GLuint texobj; Display *dpy; unsigned int refcount; struct _TexFontInfo *next; } TexFontInfo; typedef struct { short width; } PerGlyphInfo, *PerGlyphInfoPtr; typedef struct _TexFont { TexFontInfo *tfi; Tk_Font tkfont; unsigned int tex_width; unsigned int tex_height; int ascent; int descent; unsigned int max_char_width; unsigned char *teximage; unsigned int num_glyphs; PerGlyphInfo *glyph; ZnTexGVI *tgvi; Tcl_HashEntry *hash; } TexFont; typedef struct _DeferredGLGlyphs { ZnWInfo *wi; TexFont *txf; } DeferredGLGlyphsStruct; ZnList DeferredGLGlyphs; #ifndef PTK_800 #include "CharsetUTF8.h" #else #include "CharsetISO8859-15.h" #endif #ifndef PTK_800 #define AA_FONTS 1 #endif static void SuckGlyphsFromServer(ZnWInfo *wi, TexFont *txf) { Pixmap offscreen = 0; XImage *image = NULL; #ifdef AA_FONTS XColor *white, *black; unsigned long pixel; #endif GC xgc = 0; unsigned int height, length, pixwidth; unsigned int i, j, use_max_width; unsigned int wgap=2, hgap=2, tex_width, tex_height; unsigned char *to; unsigned int x, y; unsigned int width=0, maxSpanLength; int grabList[MAX_GLYPHS_PER_GRAB]; unsigned int glyphsPerGrab = MAX_GLYPHS_PER_GRAB; unsigned int numToGrab, glyph; ZnTexGVI *tgvip; Tk_FontMetrics fm; CONST unsigned char *cur, *next; #ifndef PTK_800 Tcl_UniChar uni_ch; #endif ZnGLContextEntry *ce = ZnGetGLContext(wi->dpy); //printf("loading a font \n"); Tk_GetFontMetrics(txf->tkfont, &fm); #ifndef PTK_800 txf->num_glyphs = Tcl_NumUtfChars(ZnDefaultCharset, strlen(ZnDefaultCharset)); #else txf->num_glyphs = strlen(ZnDefaultCharset); #endif txf->glyph = ZnMalloc(txf->num_glyphs * sizeof(PerGlyphInfo)); if (!txf->glyph) { goto FreeAndReturn; } txf->tgvi = ZnMalloc(txf->num_glyphs * sizeof(ZnTexGVI)); if (!txf->tgvi) { goto FreeAndReturn; } txf->ascent = fm.ascent; txf->descent = fm.descent; txf->max_char_width = 0; tex_width = tex_height = 0; use_max_width = 0; height = txf->ascent + txf->descent; tgvip = txf->tgvi; /* * Compute the max character size in the font. This may be * a bit heavy hammer style but it avoid guessing on characters * not available in the font. */ cur = ZnDefaultCharset; i = 0; while (*cur) { #ifndef PTK_800 next = Tcl_UtfNext(cur); #else next = cur + 1; #endif Tk_MeasureChars(txf->tkfont, cur, next - cur, 0, TK_AT_LEAST_ONE, &length); txf->glyph[i].width = length; txf->max_char_width = MAX(txf->max_char_width, length); if (tex_width + length + wgap > ce->max_tex_size) { tex_width = 0; use_max_width = 1; tex_height += height + hgap; if ((tex_height > ce->max_tex_size) || (length > ce->max_tex_size)) { goto FreeAndReturn; } } tgvip->v0x = 0; tgvip->v0y = txf->descent - height; tgvip->v1x = length; tgvip->v1y = txf->descent; tgvip->t0x = (GLfloat) tex_width; tgvip->t0y = (GLfloat) tex_height; tgvip->t1x = tgvip->t0x + length; tgvip->t1y = tgvip->t0y + height; tgvip->advance = (GLfloat) length; #ifndef PTK_800 Tcl_UtfToUniChar(cur, &uni_ch); tgvip->code = uni_ch; #else tgvip->code = *cur; #endif tex_width += length + wgap; cur = next; i++; tgvip++; } // // If max_char_width is still not known, perhaps because of a bogus // font with character width not documented, set the width to an // arbitrary value. if (!txf->max_char_width) { txf->max_char_width = 1; } if (use_max_width) { tex_width = ce->max_tex_size; } tex_height += height; /* * Round the texture size to the next power of two. */ tex_height = To2Power(tex_height); tex_width = To2Power(tex_width); if ((tex_height > ce->max_tex_size) || (tex_width > ce->max_tex_size)) { fprintf(stderr, "Font doesn't fit into a texture\n"); goto FreeAndReturn; } txf->tex_width = tex_width; txf->tex_height = tex_height; //printf("(%s) Texture size is %d x %d for %d chars (max size: %d)\n", // Tk_NameOfFont(txf->tkfont), txf->tex_width, txf->tex_height, txf->num_glyphs, ce->max_tex_size); /* * Now render the font bits into the texture. */ txf->teximage = ZnMalloc(tex_height * tex_width); if (!txf->teximage) { goto FreeAndReturn; } #ifndef AA_FONTS memset(txf->teximage, 0, tex_height * tex_width); #endif maxSpanLength = (txf->max_char_width + 7) / 8; /* Be careful determining the width of the pixmap; the X protocol allows pixmaps of width 2^16-1 (unsigned short size) but drawing coordinates max out at 2^15-1 (signed short size). Some problems have been encountered with XFree86, it is safe to limit the width to 4096. */ if ((glyphsPerGrab * 8 * maxSpanLength) >= (1 << 12)) { glyphsPerGrab = (1 << 12) / (8 * maxSpanLength); } pixwidth = glyphsPerGrab * 8 * maxSpanLength; #ifdef AA_FONTS offscreen = Tk_GetPixmap(wi->dpy, RootWindowOfScreen(wi->screen), (int) pixwidth, (int) height, Tk_Depth(wi->win)); #else offscreen = Tk_GetPixmap(wi->dpy, RootWindowOfScreen(wi->screen), (int) pixwidth, (int) height, 1); #endif xgc = XCreateGC(wi->dpy, offscreen, 0, NULL); #ifdef AA_FONTS white = Tk_GetColor(wi->interp, wi->win, "white"); black = Tk_GetColor(wi->interp, wi->win, "black"); XSetForeground(wi->dpy, xgc, white->pixel); XSetBackground(wi->dpy, xgc, white->pixel); #else XSetForeground(wi->dpy, xgc, WhitePixelOfScreen(wi->screen)); XSetBackground(wi->dpy, xgc, WhitePixelOfScreen(wi->screen)); #endif XFillRectangle(wi->dpy, offscreen, xgc, 0, 0, pixwidth, height); #ifdef AA_FONTS XSetForeground(wi->dpy, xgc, black->pixel); #else XSetForeground(wi->dpy, xgc, BlackPixelOfScreen(wi->screen)); #endif XSetFont(wi->dpy, xgc, Tk_FontId(txf->tkfont)); numToGrab = 0; cur = ZnDefaultCharset; i = 0; while (*cur) { #ifndef PTK_800 next = Tcl_UtfNext(cur); #else next = cur + 1; #endif if (txf->glyph[i].width != 0) { Tk_DrawChars(wi->dpy, offscreen, xgc, txf->tkfont, cur, next - cur, (int) (8*maxSpanLength*numToGrab), txf->ascent); grabList[numToGrab] = i; numToGrab++; } if ((numToGrab >= glyphsPerGrab) || (i == txf->num_glyphs - 1)) { #ifdef AA_FONTS image = XGetImage(wi->dpy, offscreen, 0, 0, pixwidth, height, AllPlanes, ZPixmap); #else image = XGetImage(wi->dpy, offscreen, 0, 0, pixwidth, height, 1, XYPixmap); #endif for (j = 0; j < numToGrab; j++) { glyph = grabList[j]; width = txf->glyph[glyph].width; tgvip = &txf->tgvi[glyph]; to = txf->teximage + (int) (tgvip->t0y * tex_width) + (int) tgvip->t0x; tgvip->t0x = tgvip->t0x / (GLfloat) tex_width; tgvip->t0y = tgvip->t0y / (GLfloat) tex_height; tgvip->t1x = tgvip->t1x / (GLfloat) tex_width; tgvip->t1y = tgvip->t1y / (GLfloat) tex_height; for (y = 0; y < height; y++) { for (x = 0; x < width; x++, to++) { /* XXX The algorithm used to suck across the font ensures that each glyph begins on a byte boundary. In theory this would make it convienent to copy the glyph into a byte oriented bitmap. We actually use the XGetPixel function to extract each pixel from the image which is not that efficient. We could either do tighter packing in the pixmap or more efficient extraction from the image. Oh well. */ #ifdef AA_FONTS pixel = XGetPixel(image, (int) (j*maxSpanLength*8) + x, y); *to = 255 - (pixel & 0xFF); #else if (XGetPixel(image, (int) (j*maxSpanLength*8) + x, y) == BlackPixelOfScreen(wi->screen)) { *to = 255; } #endif } to += tex_width - width; } } XDestroyImage(image); image = NULL; numToGrab = 0; /* do we need to clear the offscreen pixmap to get more? */ if (i < txf->num_glyphs - 1) { #ifdef AA_FONTS XSetForeground(wi->dpy, xgc, white->pixel); #else XSetForeground(wi->dpy, xgc, WhitePixelOfScreen(wi->screen)); #endif XFillRectangle(wi->dpy, offscreen, xgc, 0, 0, 8 * maxSpanLength * glyphsPerGrab, height); #ifdef AA_FONTS XSetForeground(wi->dpy, xgc, black->pixel); #else XSetForeground(wi->dpy, xgc, BlackPixelOfScreen(wi->screen)); #endif } } cur = next; i++; } #ifdef AA_FONTS Tk_FreeColor(black); Tk_FreeColor(white); #endif XFreeGC(wi->dpy, xgc); Tk_FreePixmap(wi->dpy, offscreen); return; FreeAndReturn: if (txf->glyph) { ZnFree(txf->glyph); txf->glyph = NULL; } if (txf->tgvi) { ZnFree(txf->tgvi); txf->tgvi = NULL; } if (txf->teximage) { ZnFree(txf->teximage); txf->teximage = NULL; } ZnWarning("Cannot load font texture for font "); ZnWarning(Tk_NameOfFont(txf->tkfont)); ZnWarning("\n"); } static void ZnNeedToGetGLGlyphs(ZnWInfo *wi, TexFont *txf) { DeferredGLGlyphsStruct dgg, *dggp; int i, num; if (!DeferredGLGlyphs) { DeferredGLGlyphs = ZnListNew(4, sizeof(DeferredGLGlyphsStruct)); } dggp = ZnListArray(DeferredGLGlyphs); num = ZnListSize(DeferredGLGlyphs); for (i = 0; i < num; i++, dggp++) { if (dggp->txf == txf) { return; } } dgg.wi = wi; dgg.txf = txf; ZnListAdd(DeferredGLGlyphs, &dgg, ZnListTail); /*printf("adding a font to load\n");*/ } void ZnGetDeferredGLGlyphs(void) { DeferredGLGlyphsStruct *dggp; int i, num = ZnListSize(DeferredGLGlyphs); if (!num) { return; } dggp = ZnListArray(DeferredGLGlyphs); for (i = 0; i < num; i++, dggp++) { SuckGlyphsFromServer(dggp->wi, dggp->txf); } ZnListEmpty(DeferredGLGlyphs); } static void ZnRemovedDeferredGLGlyph(TexFont *txf) { DeferredGLGlyphsStruct *dggp; int i, num; dggp = ZnListArray(DeferredGLGlyphs); num = ZnListSize(DeferredGLGlyphs); for (i = 0; i < num; i++, dggp++) { if (dggp->txf == txf) { ZnListDelete(DeferredGLGlyphs, i); return; } } } /* ********************************************************************************** * * ZnGetTexFont -- * ********************************************************************************** */ ZnTexFontInfo ZnGetTexFont(ZnWInfo *wi, Tk_Font font) { TexFont *txf; TexFontInfo *tfi; static int inited = 0; Tcl_HashEntry *entry; int new; if (!inited) { Tcl_InitHashTable(&font_textures, TCL_STRING_KEYS); inited = 1; } /* printf("family: %s, size: %d, weight: %d, slant: %d, underline: %d, overstrike: %d\n", tft->fa.family, tft->fa.size, tft->fa.weight, tft->fa.slant, tft->fa.underline, tft->fa.overstrike); */ entry = Tcl_FindHashEntry(&font_textures, Tk_NameOfFont(font)); if (entry != NULL) { /*printf("Found an already created font %s\n", Tk_NameOfFont(font));*/ txf = (TexFont *) Tcl_GetHashValue(entry); } else { /*printf("Creating a new texture font for %s\n", Tk_NameOfFont(font));*/ txf = ZnMalloc(sizeof(TexFont)); if (txf == NULL) { return NULL; } txf->tfi = NULL; txf->tgvi = NULL; txf->glyph = NULL; txf->teximage = NULL; /* Get a local reference to the font, it will be deallocated * when no further references on this TexFont exist. */ txf->tkfont = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(font)); /*printf("Scheduling glyph loading for font %s\n", ZnNameOfTexFont(tfi));*/ ZnNeedToGetGLGlyphs(wi, txf); entry = Tcl_CreateHashEntry(&font_textures, Tk_NameOfFont(font), &new); Tcl_SetHashValue(entry, (ClientData) txf); txf->hash = entry; } /* * Now locate the texture obj in the texture list for this widget. */ for (tfi = txf->tfi; tfi != NULL; tfi = tfi->next) { if (tfi->dpy == wi->dpy) { tfi->refcount++; return tfi; } } /* * Not found allocate a new texture object. */ tfi = ZnMalloc(sizeof(TexFontInfo)); if (tfi == NULL) { return NULL; } tfi->refcount = 1; tfi->dpy = wi->dpy; tfi->txf = txf; tfi->texobj = 0; tfi->next = txf->tfi; txf->tfi = tfi; return tfi; } /* ********************************************************************************** * * ZnNameOfTexFont -- * ********************************************************************************** */ char const * ZnNameOfTexFont(ZnTexFontInfo tfi) { return Tk_NameOfFont(((TexFontInfo *) tfi)->txf->tkfont); } /* ********************************************************************************** * * ZnTexFontTex -- * ********************************************************************************** */ GLuint ZnTexFontTex(ZnTexFontInfo tfi) { TexFontInfo *this = (TexFontInfo *) tfi; TexFont *txf = this->txf; if (!txf->teximage) { return 0; } if (!this->texobj) { glGenTextures(1, &this->texobj); /*printf("%d creation texture %d pour la fonte %s\n", this->dpy, this->texobj, ZnNameOfTexFont(tfi));*/ glBindTexture(GL_TEXTURE_2D, this->texobj); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glGetError(); //printf("Demande texture de %d x %d pour %s\n", // txf->tex_width, txf->tex_height, ZnNameOfTexFont(tfi)); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, txf->tex_width, txf->tex_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, txf->teximage); if (glGetError() != GL_NO_ERROR) { ZnWarning("Can't allocate the texture for font "); ZnWarning(ZnNameOfTexFont(tfi)); ZnWarning("\n"); } glBindTexture(GL_TEXTURE_2D, 0); } /*printf("%d utilisation de la texture %d\n", this->wi, this->texobj);*/ return this->texobj; } /* ********************************************************************************** * * ZnFreeTexFont -- * ********************************************************************************** */ void ZnFreeTexFont(ZnTexFontInfo tfi) { TexFontInfo *this = ((TexFontInfo *) tfi); TexFont *txf = this->txf; TexFontInfo *prev, *scan; for (prev=NULL, scan=this->txf->tfi; (scan!=NULL)&&(scan != this); prev=scan, scan=scan->next); if (scan != this) { return; } /* * Decrement tex font object refcount. */ this->refcount--; if (this->refcount != 0) { return; } /* * Unlink the deleted tex font info. */ if (prev == NULL) { txf->tfi = this->next; } else { prev->next = this->next; } if (this->texobj) { ZnGLContextEntry *ce; /*printf("%d Freeing texture %d from font %s\n", this->dpy, this->texobj, ZnNameOfTexFont(tfi));*/ ce = ZnGLMakeCurrent(this->dpy, 0); if (ce) { glDeleteTextures(1, &this->texobj); ZnGLReleaseContext(ce); } } /* * Remove the font from the deferred load list */ ZnRemovedDeferredGLGlyph(txf); /* * There is no more client for this font * deallocate the structures. */ if (txf->tfi == NULL) { /*printf("%d Freeing txf for %s\n", this, ZnNameOfTexFont(tfi));*/ Tk_FreeFont(txf->tkfont); ZnFree(txf->glyph); ZnFree(txf->tgvi); ZnFree(txf->teximage); Tcl_DeleteHashEntry(txf->hash); ZnFree(txf); } ZnFree(this); } /* ********************************************************************************** * * ZnGetFontIndex -- * ********************************************************************************** */ int ZnGetFontIndex(ZnTexFontInfo tfi, int c) { TexFont *txf; ZnTexGVI *tgvi; int code, min, max, mid; if (c < 127) { /* * It is possible to index the points below 127. Unicode * is the same as ascii down there. */ return c - 32; } /* * Else, search by dichotomy in the remaining chars. */ txf = ((TexFontInfo *) tfi)->txf; tgvi = txf->tgvi; if (!tgvi) { return -1; } min = 127 - 32; max = txf->num_glyphs; while (min < max) { mid = (min + max) >> 1; code = tgvi[mid].code; if (c == code) { return mid; } if (c < code) { max = mid; } else { min = mid + 1; } } //fprintf(stderr, "Tried to access unavailable texture font character %d (Unicode)\n", c); return -1; } /* ********************************************************************************** * * ZnTexFontGVI -- * ********************************************************************************** */ ZnTexGVI * ZnTexFontGVI(ZnTexFontInfo tfi, int c) { TexFont *txf = ((TexFontInfo *) tfi)->txf; ZnTexGVI *tgvi = NULL; int index; index = ZnGetFontIndex(tfi, c); if (index >= 0) { tgvi = &txf->tgvi[index]; } return tgvi; } #endif