/* * Image.c -- Image support routines. * * Authors : Patrick LECOANET * Creation date : Wed Dec 8 11:04:44 1999 */ /* * Copyright (c) 1999 CENA, Patrick Lecoanet -- * * This code 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 code 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 code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include "Types.h" #include "Image.h" #include "WidgetInfo.h" #include "Geo.h" #include #ifdef GLX #include #include #include #include #include "Draw.h" #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 GLX static Tcl_HashTable font_textures; #endif typedef struct _ImageStruct { union { struct { Pixmap pixmap; Pixmap mask_pmap; Screen *screen; } x; struct { #ifdef GLX GLuint texobj; #endif struct _WidgetInfo *wi; } gl; } i; struct _ImageBits *bits; /* Bookkeeping */ ZnBool for_gl; int refcount; struct _ImageStruct *next; } ImageStruct, *Image; typedef struct _ImageBits { int width; int height; unsigned char *bpixels; /* Needed at least to know the bounds (Pick), or if * the image is a bitmap. Can be NULL if no mask is * defined for the image (i.e the image is * rectangular) and the image is not a bitmap. */ int rowstride; #ifdef GLX ZnReal t; /* Texture parameters for the image. */ ZnReal s; #endif /* Bookeeping */ Tcl_HashEntry *hash; /* From this it is easy to get the image/bitmap * name. */ XImage *ipixels; /* Keep this to create textures and pixmaps as * needed. NULL if the image is a bitmap. This * can be tested to tell if this is an image or * a bitmap. */ XImage *mask; /* Keep this to build special clip mask in X (Icon). * Can be NULL if no mask is defined for the image * (i.e the image is rectangular) or if the image * is a bitmap. */ #ifdef GLX 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 Image images; /* Linked list of widget/display dependant * specializations of this image. If NULL, the * image has no specialization and can be freed. */ } ImageBits; #ifdef GLX /* * 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, int t_width, int t_height, unsigned char *bpixels, int bstride, unsigned char *t_bits) { 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. */ if (bpixels) { alpha = ZnGetBitmapPixel(bpixels, bstride, x, y) ? 255 : 0; } else { alpha = 255; } /* * 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, int t_width, int t_height, unsigned char *bpixels, int bstride, unsigned char *t_bits) { 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. */ if (bpixels) { alpha = ZnGetBitmapPixel(bpixels, bstride, x, y) ? 255 : 0; } else { alpha = 255; } /* * 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 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) { /* * Void stub that keeps the Tk image mecanism happy. Zinc does * _not_ implement image update. */ } static void GatherImageBits(WidgetInfo *wi, Tk_Image tkimage, ImageBits *bits) { Pixmap pmap; int depth = DefaultDepthOfScreen(wi->screen); int x, y; unsigned char *line; GC gc; XImage *im1, *im2; ZnBool full_mask=True; /* * Nothing known about this image, collect the image bits. */ pmap = XCreatePixmap(wi->dpy, RootWindowOfScreen(wi->screen), bits->width, bits->height, depth); gc = XCreateGC(wi->dpy, pmap, 0, NULL); XSetForeground(wi->dpy, gc, 0); XFillRectangle(wi->dpy, pmap, gc, 0, 0, bits->width, bits->height); Tk_RedrawImage(tkimage, 0, 0, bits->width, bits->height, pmap, 0, 0); im1 = bits->ipixels = XGetImage(wi->dpy, pmap, 0, 0, bits->width, bits->height, ~0L, ZPixmap); XSetForeground(wi->dpy, gc, 1); XFillRectangle(wi->dpy, pmap, gc, 0, 0, bits->width, bits->height); Tk_RedrawImage(tkimage, 0, 0, bits->width, bits->height, pmap, 0, 0); im2 = XGetImage(wi->dpy, pmap, 0, 0, bits->width, bits->height, ~0L, ZPixmap); XFreePixmap(wi->dpy, pmap); /* * The image structure can be setup locally (TODO). */ XFreeGC(wi->dpy, gc); pmap = XCreatePixmap(wi->dpy, RootWindowOfScreen(wi->screen), bits->width, bits->height, 1); gc = XCreateGC(wi->dpy, pmap, 0, NULL); XSetForeground(wi->dpy, gc, 0); XFillRectangle(wi->dpy, pmap, gc, 0, 0, bits->width, bits->height); XFreeGC(wi->dpy, gc); bits->mask = XGetImage(wi->dpy, pmap, 0, 0, bits->width, bits->height, 1, XYPixmap); XFreePixmap(wi->dpy, pmap); bits->rowstride = bits->mask->bytes_per_line; bits->bpixels = ZnMalloc(bits->height * bits->rowstride); memset(bits->bpixels, 0, bits->height * bits->rowstride); line = bits->bpixels; for (y = 0; y < bits->height; y++) { for (x = 0; x < bits->width; x++) { if (XGetPixel(im1, x, y) == XGetPixel(im2, x, y)) { XPutPixel(bits->mask, x, y, 1L); line[x >> 3] |= 0x80 >> (x & 7); } else { full_mask = False; } } line += bits->rowstride; } XDestroyImage(im2); if (full_mask) { XDestroyImage(bits->mask); bits->mask = NULL; ZnFree(bits->bpixels); bits->bpixels = NULL; } } static Image GetImageInstance(WidgetInfo *wi, ImageBits *bits) { int depth = DefaultDepthOfScreen(wi->screen); ZnBool for_gl = wi->render>0; XGCValues values; GC gc; Image image; /* * 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->i.gl.wi == wi)) { image->refcount++; return image; } else if (!for_gl && (image->i.x.screen == wi->screen)) { image->refcount++; return image; } } } /* * Create a new instance for this case. */ image = ZnMalloc(sizeof(ImageStruct)); image->bits = bits; image->refcount = 1; image->for_gl = for_gl; if (image->for_gl) { image->i.gl.wi = wi; #ifdef GLX image->i.gl.texobj = 0; #endif } else { image->i.x.screen = wi->screen; image->i.x.pixmap = XCreatePixmap(wi->dpy, RootWindowOfScreen(wi->screen), bits->width, bits->height, depth); gc = XCreateGC(wi->dpy, image->i.x.pixmap, 0, NULL); XPutImage(wi->dpy, image->i.x.pixmap, gc, bits->ipixels, 0, 0, 0, 0, bits->width, bits->height); XFreeGC(wi->dpy, gc); if (bits->mask) { image->i.x.mask_pmap = XCreatePixmap(wi->dpy, RootWindowOfScreen(wi->screen), bits->width, bits->height, 1); values.foreground = 1; values.background = 0; gc = XCreateGC(wi->dpy, image->i.x.mask_pmap, GCForeground|GCBackground, &values); XPutImage(wi->dpy, image->i.x.mask_pmap, gc, bits->mask, 0, 0, 0, 0, bits->width, bits->height); XFreeGC(wi->dpy, gc); } else { image->i.x.mask_pmap = None; } } image->next = bits->images; bits->images = image; return image; } ZnImage ZnGetImage(WidgetInfo *wi, Tk_Uid image_name) { Tcl_HashEntry *entry; int new; ImageBits *bits; Tk_Image tkimage; /*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 déjà connue\n", image_name);*/ bits = (ImageBits *) Tcl_GetHashValue(entry); return GetImageInstance(wi, bits); } else { /*printf("Nouvelle Image %s\n", image_name);*/ if (strcmp(image_name, "") == 0) { return ZnUnspecifiedImage; } bits = ZnMalloc(sizeof(ImageBits)); tkimage = Tk_GetImage(wi->interp, wi->win, image_name, InvalidateImage, (ClientData) bits); if (tkimage == NULL) { im_val_err: ZnFree(bits); ZnWarning("unknown or bogus image \""); ZnWarning(image_name); ZnWarning("\"\n"); return ZnUnspecifiedImage; } else { Tk_SizeOfImage(tkimage, &bits->width, &bits->height); if ((bits->width == 0) || (bits->height == 0)) { Tk_FreeImage(tkimage); goto im_val_err; } } #ifdef GLX bits->t_bits = NULL; #endif bits->images = NULL; bits->mask = NULL; bits->bpixels = NULL; entry = Tcl_CreateHashEntry(&images, image_name, &new); bits->hash = entry; Tcl_SetHashValue(entry, (ClientData) bits); } if (!bits->ipixels) { /* * First time use of the image, read in * the bits and free the Tk image. */ GatherImageBits(wi, tkimage, bits); Tk_FreeImage(tkimage); } return GetImageInstance(wi, bits); } /* ********************************************************************************** * * ZnGetBitmap -- * ********************************************************************************** */ ZnImage ZnGetBitmap(WidgetInfo *wi, Tk_Uid bitmap_name) { Tcl_HashEntry *entry; ImageBits *bits; Pixmap pmap; XImage *mask; Image image; ZnBool for_gl = wi->render>0; int x, y, new, width, height; unsigned char *line; /*printf("ZnGetBitmap: %s\n", bitmap_name);*/ if (!images_inited) { Tcl_InitHashTable(&images, TCL_STRING_KEYS); images_inited = 1; } bitmap_name = Tk_GetUid(bitmap_name); entry = Tcl_FindHashEntry(&images, bitmap_name); if (entry != NULL) { bits = (ImageBits *) Tcl_GetHashValue(entry); } else { pmap = Tk_GetBitmap(wi->interp, wi->win, bitmap_name); if (pmap == ZnUnspecifiedImage) { return ZnUnspecifiedImage; } Tk_SizeOfBitmap(wi->dpy, pmap, &width, &height); bits = ZnMalloc(sizeof(ImageBits)); bits->width = width; bits->height = height; #ifdef GLX bits->t_bits = NULL; #endif bits->images = NULL; bits->mask = NULL; bits->bpixels = NULL; bits->ipixels = NULL; mask = XGetImage(wi->dpy, pmap, 0, 0, width, height, 1L, XYPixmap); bits->rowstride = mask->bytes_per_line; bits->bpixels = ZnMalloc(height * bits->rowstride); memset(bits->bpixels, 0, height * bits->rowstride); line = bits->bpixels; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { if (XGetPixel(mask, x, y)) { line[x >> 3] |= 0x80 >> (x & 7); } } line += bits->rowstride; } XDestroyImage(mask); Tk_FreeBitmap(wi->dpy, pmap); entry = Tcl_CreateHashEntry(&images, bitmap_name, &new); Tcl_SetHashValue(entry, (ClientData) bits); bits->hash = entry; } /* * 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->i.gl.wi == wi)) { image->refcount++; return image; } else if (!for_gl && (image->i.x.screen == wi->screen)) { image->refcount++; return image; } } } /* * Create a new instance for this widget/display conf. */ image = ZnMalloc(sizeof(ImageStruct)); image->bits = bits; image->refcount = 1; image->for_gl = for_gl; if (image->for_gl) { image->i.gl.wi = wi; #ifdef GLX image->i.gl.texobj = 0; #endif } else { image->i.x.screen = wi->screen; image->i.x.mask_pmap = None; /* * Need to get a pixmap that match this dpy. */ image->i.x.pixmap = Tk_GetBitmap(wi->interp, wi->win, bitmap_name); } image->next = bits->images; bits->images = image; return image; } /* ********************************************************************************** * * ZnGetImageByValue -- * ********************************************************************************** */ ZnImage ZnGetImageByValue(ZnImage image) { ((Image) image)->refcount++; return image; } /* ********************************************************************************** * * ZnFreeImage -- * ********************************************************************************** */ void ZnFreeImage(ZnImage image) { Image prev, scan, this = ((Image) image); ImageBits *bits = this->bits; /* * 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 ? */ } this->refcount--; if (this->refcount != 0) { return; } /* * Unlink the deleted image instance. */ if (prev == NULL) { bits->images = this->next; } else { prev->next = this->next; } if (this->for_gl) { #ifdef GLX WidgetInfo *wi = this->i.gl.wi; if (this->i.gl.texobj && wi->win) { glXMakeCurrent(wi->dpy, ZnWindowId(wi->win), wi->gl_context); glDeleteTextures(1, &this->i.gl.texobj); } #endif } else if (bits->ipixels) { /* * This is an image, we need to free the pixmaps. */ if (this->i.x.pixmap != None) { XFreePixmap(DisplayOfScreen(this->i.x.screen), this->i.x.pixmap); } if (this->i.x.mask_pmap != None) { XFreePixmap(DisplayOfScreen(this->i.x.screen), this->i.x.mask_pmap); } } else { /* * This is a bitmap ask Tk to free the resource. */ Tk_FreeBitmap(DisplayOfScreen(this->i.x.screen), this->i.x.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 GLX if (bits->t_bits) { ZnFree(bits->t_bits); } #endif if (bits->mask) { XDestroyImage(bits->mask); } if (bits->ipixels) { XDestroyImage(bits->ipixels); } if (bits->bpixels) { ZnFree(bits->bpixels); } 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 im = (Image) image; *width = im->bits->width; *height = im->bits->height; } /* ********************************************************************************** * * ZnImagePattern -- * ********************************************************************************** */ char * ZnImagePattern(ZnImage image, int *stride) { if (stride) { *stride = ((Image) image)->bits->rowstride; } return ((Image) image)->bits->bpixels; } /* ********************************************************************************** * * ZnImageIsBitmap -- * ********************************************************************************** */ ZnBool ZnImageIsBitmap(ZnImage image) { return (((Image) image)->bits->ipixels == NULL); } /* ********************************************************************************** * * ZnImageMask -- * ********************************************************************************** */ XImage * ZnImageMask(ZnImage image) { return ((Image) image)->bits->mask; } /* ********************************************************************************** * * ZnImagePixmap -- * ********************************************************************************** */ Pixmap ZnImagePixmap(ZnImage image, Pixmap *mask_pmap) { if (((Image) image)->for_gl) { printf("Bogus use of an image, it was created for GL and use in an X11 context\n"); return None; } if (mask_pmap) { *mask_pmap = ((Image) image)->i.x.mask_pmap; } return ((Image) image)->i.x.pixmap; } /* ********************************************************************************** * * ZnImageTex -- * ********************************************************************************** */ #ifdef GLX GLuint ZnImageTex(ZnImage image, ZnReal *t, ZnReal *s) { Image this = (Image) image; ImageBits *bits = this->bits; ZnBool is_bmap = ZnImageIsBitmap(image); int depth, t_size; if (!this->for_gl) { return 0; } if (!bits->t_bits) { /*printf("chargement texture pour image %s\n", ZnNameOfImage(this));*/ bits->t_width = To2Power(bits->width); bits->t_height = To2Power(bits->height); bits->s = bits->width / (ZnReal) bits->t_width; bits->t = bits->height / (ZnReal) bits->t_height; if (is_bmap) { 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 < bits->height; i++) { d = dstart; o = ostart; for (j = 0; j < bits->width; j++) { *d++ = ZnGetBitmapPixel(bits->bpixels, bits->rowstride, j, i) ? 255 : 0; } ostart += bits->rowstride; dstart += bits->t_width; } } else { t_size = bits->t_width * 4 * bits->t_height; bits->t_bits = ZnMalloc(t_size); if (this->for_gl) { depth = DefaultDepthOfScreen(this->i.gl.wi->screen); } else { depth = DefaultDepthOfScreen(this->i.x.screen); } if (depth == 16) { From5r6g5b(bits->ipixels->data, bits->width, bits->height, bits->ipixels->bytes_per_line, bits->t_width, bits->t_height, bits->bpixels, bits->rowstride, bits->t_bits); } else if ((depth == 24) || (depth == 32)) { From8r8g8b(bits->ipixels->data, bits->width, bits->height, bits->ipixels->bytes_per_line, bits->t_width, bits->t_height, bits->bpixels, bits->rowstride, bits->t_bits); } } } if (!this->i.gl.texobj) { glGenTextures(1, &this->i.gl.texobj); /*printf("creation texture %d pour image %s\n", this->i.gl.texobj, ZnNameOfImage(this));*/ glBindTexture(GL_TEXTURE_2D, this->i.gl.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); if (ZnImageIsBitmap(image)) { 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); } glBindTexture(GL_TEXTURE_2D, 0); } *t = this->bits->t; *s = this->bits->s; return this->i.gl.texobj; } #endif #ifdef GLX /* Copyright (c) Mark J. Kilgard, 1997. */ /* This program is freely distributable without licensing fees and is provided without guarantee or warrantee expressed or implied. This program is -not- in the public domain. */ #define MAX_GLYPHS_PER_GRAB 512 /* this is big enough for 2^9 glyph * character sets */ typedef struct { unsigned short c; /* Potentially support 16-bit glyphs. */ unsigned char width; unsigned char height; char xoffset; char yoffset; char advance; char dummy; /* Space holder for alignment reasons. */ short x; short y; } TexGlyphInfo; typedef struct _TexFontInfo { GLuint texobj; struct _TexFont *txf; WidgetInfo *wi; int refcount; struct _TexFontInfo *next; } TexFontInfo; typedef struct _TexFont { TexFontInfo *tfi; ZnFont tkfont; int tex_width; int tex_height; int max_ascent; int max_descent; int num_glyphs; int min_glyph; int range; unsigned char *teximage; TexGlyphInfo *tgi; ZnTexGVI *tgvi; ZnTexGVI **lut; Tcl_HashEntry *hash; } TexFont; typedef struct { short width; short height; short xoffset; short yoffset; short advance; unsigned char *bitmap; } PerGlyphInfo, *PerGlyphInfoPtr; typedef struct { int min_char; int max_char; int max_ascent; int max_descent; int num_glyphs; PerGlyphInfo glyph[1]; } FontInfo, *FontInfoPtr; static FontInfoPtr fontinfo; void getMetric(FontInfoPtr font, int c, TexGlyphInfo *tgi) { PerGlyphInfoPtr glyph; unsigned char *bitmapData; tgi->c = c; if ((c < font->min_char) || (c > font->max_char)) { tgi->width = 0; tgi->height = 0; tgi->xoffset = 0; tgi->yoffset = 0; tgi->dummy = 0; tgi->advance = 0; return; } glyph = &font->glyph[c - font->min_char]; bitmapData = glyph->bitmap; if (bitmapData) { tgi->width = glyph->width; tgi->height = glyph->height; tgi->xoffset = glyph->xoffset; tgi->yoffset = glyph->yoffset; } else { tgi->width = 0; tgi->height = 0; tgi->xoffset = 0; tgi->yoffset = 0; } tgi->dummy = 0; /* printf("\'%c\' %d\n", c, glyph->advance);*/ tgi->advance = glyph->advance; } int glyphCompare(const void *a, const void *b) { unsigned char *c1 = (unsigned char *) a; unsigned char *c2 = (unsigned char *) b; TexGlyphInfo tgi1; TexGlyphInfo tgi2; getMetric(fontinfo, *c1, &tgi1); getMetric(fontinfo, *c2, &tgi2); return tgi2.height - tgi1.height; } void placeGlyph(FontInfoPtr font, int c, unsigned char *texarea, int stride, int x, int y) { PerGlyphInfoPtr glyph; unsigned char *bitmapData; int width, height, spanLength; int i, j; /*printf("x: %d, y: %d, c: %d, texarea: 0x%X, stride: %d\n", x, y, c, texarea, stride);*/ if ((c < font->min_char) || (c > font->max_char)) { return; } glyph = &font->glyph[c - font->min_char]; bitmapData = glyph->bitmap; if (bitmapData) { width = glyph->width; spanLength = (width + 7) / 8; height = glyph->height; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { texarea[stride * (y+i) + x + j] = (bitmapData[i*spanLength + j/8] & (1<<(j&7))) ? 255 : 0; } } } } FontInfoPtr SuckGlyphsFromServer(ZnWindow win, ZnFont font) { Display *dpy = Tk_Display(win); XFontStruct *fontinfo = NULL; Pixmap offscreen = 0; XImage *image = NULL; GC xgc = 0; XGCValues values; int width, height, pixwidth; int i, j; XCharStruct *charinfo=NULL; XChar2b character; unsigned char *bitmapData = NULL; int x, y; int numchars, spanLength=0; int charWidth=0, charHeight=0, maxSpanLength; int grabList[MAX_GLYPHS_PER_GRAB]; int glyphsPerGrab = MAX_GLYPHS_PER_GRAB; int numToGrab, thisglyph; FontInfoPtr myfontinfo = NULL; fontinfo = XQueryFont(dpy, Tk_FontId(font)); if (!fontinfo) { return NULL; } numchars = fontinfo->max_char_or_byte2 - fontinfo->min_char_or_byte2 + 1; if (numchars < 1) { return NULL; } myfontinfo = (FontInfoPtr) ZnMalloc(sizeof(FontInfo) + (numchars - 1) * sizeof(PerGlyphInfo)); if (!myfontinfo) { return NULL; } myfontinfo->num_glyphs = numchars; myfontinfo->min_char = fontinfo->min_char_or_byte2; myfontinfo->max_char = fontinfo->max_char_or_byte2; myfontinfo->max_ascent = fontinfo->max_bounds.ascent; myfontinfo->max_descent = fontinfo->max_bounds.descent; width = fontinfo->max_bounds.rbearing - fontinfo->min_bounds.lbearing; height = fontinfo->max_bounds.ascent + fontinfo->max_bounds.descent; maxSpanLength = (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). If the width is too large, we need to limit the glyphs per grab. */ if ((glyphsPerGrab * 8 * maxSpanLength) >= (1 << 15)) { glyphsPerGrab = (1 << 15) / (8 * maxSpanLength); } pixwidth = glyphsPerGrab * 8 * maxSpanLength; offscreen = XCreatePixmap(dpy, RootWindow(dpy, DefaultScreen(dpy)), pixwidth, height, 1); values.font = Tk_FontId(font); values.background = 0; values.foreground = 0; xgc = XCreateGC(dpy, offscreen, GCFont | GCBackground | GCForeground, &values); XFillRectangle(dpy, offscreen, xgc, 0, 0, 8 * maxSpanLength * glyphsPerGrab, height); XSetForeground(dpy, xgc, 1); numToGrab = 0; if (fontinfo->per_char == NULL) { charinfo = &(fontinfo->min_bounds); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; spanLength = (charWidth + 7) / 8; } for (i = 0; i < myfontinfo->num_glyphs; i++) { if (fontinfo->per_char != NULL) { charinfo = &(fontinfo->per_char[i]); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; if (charWidth == 0 || charHeight == 0) { /* Still must move raster pos even if empty character */ myfontinfo->glyph[i].width = 0; myfontinfo->glyph[i].height = 0; myfontinfo->glyph[i].xoffset = 0; myfontinfo->glyph[i].yoffset = 0; myfontinfo->glyph[i].advance = charinfo->width; myfontinfo->glyph[i].bitmap = NULL; goto PossiblyDoGrab; } } grabList[numToGrab] = i; /* XXX is this right for large fonts? */ character.byte2 = (i + fontinfo->min_char_or_byte2) & 255; character.byte1 = (i + fontinfo->min_char_or_byte2) >> 8; XDrawString16(dpy, offscreen, xgc, -charinfo->lbearing + 8 * maxSpanLength * numToGrab, charinfo->ascent, &character, 1); numToGrab++; PossiblyDoGrab: if ((numToGrab >= glyphsPerGrab) || (i == myfontinfo->num_glyphs - 1)) { image = XGetImage(dpy, offscreen, 0, 0, pixwidth, height, 1, XYPixmap); for (j = 0; j < numToGrab; j++) { thisglyph = grabList[j]; if (fontinfo->per_char != NULL) { charinfo = &(fontinfo->per_char[thisglyph]); charWidth = charinfo->rbearing - charinfo->lbearing; charHeight = charinfo->ascent + charinfo->descent; spanLength = (charWidth + 7) / 8; } bitmapData = ZnMalloc(height * spanLength * sizeof(char)); if (bitmapData == NULL) { goto FreeFontAndReturn; } memset(bitmapData, 0, height * spanLength * sizeof(char)); for (y = 0; y < charHeight; y++) { for (x = 0; x < charWidth; x++) { /* 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. */ if (XGetPixel(image, j * maxSpanLength * 8 + x, y)) { bitmapData[y * spanLength + x / 8] |= (1 << (x & 7)); } } } myfontinfo->glyph[thisglyph].width = charWidth; myfontinfo->glyph[thisglyph].height = charHeight; myfontinfo->glyph[thisglyph].xoffset = charinfo->lbearing; myfontinfo->glyph[thisglyph].yoffset = charinfo->descent; myfontinfo->glyph[thisglyph].advance = charinfo->width; myfontinfo->glyph[thisglyph].bitmap = bitmapData; } XDestroyImage(image); numToGrab = 0; /* do we need to clear the offscreen pixmap to get more? */ if (i < myfontinfo->num_glyphs - 1) { XSetForeground(dpy, xgc, 0); XFillRectangle(dpy, offscreen, xgc, 0, 0, 8 * maxSpanLength * glyphsPerGrab, height); XSetForeground(dpy, xgc, 1); } } } XFreeFontInfo(NULL, fontinfo, 1); XFreeGC(dpy, xgc); XFreePixmap(dpy, offscreen); return myfontinfo; FreeFontAndReturn: XFreeFontInfo(NULL, fontinfo, 1); XDestroyImage(image); XFreeGC(dpy, xgc); XFreePixmap(dpy, offscreen); for (i = 0; i < myfontinfo->num_glyphs; i++) { if (myfontinfo->glyph[i].bitmap) ZnFree(myfontinfo->glyph[i].bitmap); } ZnFree(myfontinfo); return NULL; } /* ********************************************************************************** * * ZnGetTexFont -- * ********************************************************************************** */ ZnTexFontInfo ZnGetTexFont(WidgetInfo *wi, ZnFont font) { TexFont *txf; TexFontInfo *tfi; static int inited = 0; Tcl_HashEntry *entry; char *fontname = Tk_NameOfFont(font); int new; if (!inited) { Tcl_InitHashTable(&font_textures, TCL_STRING_KEYS); inited = 1; } entry = Tcl_FindHashEntry(&font_textures, fontname); if (entry != NULL) { /*printf("found font: %d |%s|\n", wi, fontname);*/ txf = (TexFont *) Tcl_GetHashValue(entry); } else { /*printf("new font: %d |%s|\n", wi, fontname);*/ txf = ZnMalloc(sizeof(TexFont)); if (txf == NULL) { return NULL; } txf->tfi = NULL; txf->tgi = NULL; txf->tgvi = NULL; txf->lut = NULL; txf->teximage = NULL; txf->tkfont = font; entry = Tcl_CreateHashEntry(&font_textures, fontname, &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->wi == wi) { tfi->refcount++; return tfi; } } /* * Not found allocate a new texture object. */ tfi = ZnMalloc(sizeof(TexFontInfo)); if (tfi == NULL) { ZnFree(txf); return NULL; } tfi->refcount = 1; tfi->texobj = 0; tfi->wi = wi; tfi->txf = txf; tfi->next = txf->tfi; txf->tfi = tfi; return tfi; } /* ********************************************************************************** * * ZnNameOfTexFont -- * ********************************************************************************** */ char * ZnNameOfTexFont(ZnTexFontInfo tfi) { return Tcl_GetHashKey(&font_textures, ((TexFontInfo *) tfi)->txf->hash); } /* ********************************************************************************** * * ZnTexFontTex -- * ********************************************************************************** */ GLuint ZnTexFontTex(ZnTexFontInfo tfi) { TexFontInfo *this = (TexFontInfo *) tfi; TexFont *txf = this->txf; unsigned char *glisto = "\t\x14\x15\x16\x17 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijmklmnopqrstuvwxyz{|}~°ÀÂÇÈÉÊËÎÏÔÙÛÜàâçèéêëîïôùûü~`~"; unsigned char *glist=NULL, *glist2=NULL; TexGlyphInfo *tgi; int i, j; int min_glyph, max_glyph; int gap = 1; /* gap between glyphs */ int px, py, maxheight; int width, height; GLfloat xstep, ystep, texw, texh; GLuint max_tex_size[1]; if (!txf->teximage) { /*printf("Chargement de la texture pour la fonte %s\n", ZnNameOfTexFont(tfi));*/ glGetIntegerv(GL_MAX_TEXTURE_SIZE, max_tex_size); fontinfo = SuckGlyphsFromServer(this->wi->win, txf->tkfont); if (fontinfo == NULL) { goto error; } txf->max_ascent = fontinfo->max_ascent; txf->max_descent = fontinfo->max_descent; txf->num_glyphs = strlen(glisto); /* * Initial size of texture. * Assume that max_tex_size is at least 128 texels. */ texw = 128; texh = 64; while (texh < txf->max_ascent+txf->max_descent) { texh *= 2; } if (texh > max_tex_size[0]) { goto error; } xstep = 0/*0.5 / texw*/; ystep = 0/*0.5 / texh*/; txf->teximage = ZnMalloc(texw * texh * sizeof(unsigned char)); if (txf->teximage == NULL) { goto error; } /*memset(txf->teximage, 0x55, texw * texh * sizeof(unsigned char));*/ txf->tgi = ZnMalloc(txf->num_glyphs * sizeof(TexGlyphInfo)); if (txf->tgi == NULL) { goto error; } txf->tgvi = ZnMalloc(txf->num_glyphs * sizeof(ZnTexGVI)); if (txf->tgvi == NULL) { goto error; } glist = ZnMalloc((txf->num_glyphs+1) * sizeof(unsigned char)); strcpy(glist, glisto); qsort(glist, txf->num_glyphs, sizeof(unsigned char), glyphCompare); /* * Keep a cache a the sorted list in case we need to * restart the allocation process. */ glist2 = ZnMalloc((txf->num_glyphs+1) * sizeof(unsigned char)); strcpy(glist2, glist); restart: px = gap; py = gap; maxheight = 0; for (i = 0; i < txf->num_glyphs; i++) { if (glist[i] != 0) { /* If not already processed... */ int foundWidthFit = 0; int c; /* Try to find a character from the glist that will fit on the remaining space on the current row. */ tgi = &txf->tgi[i]; getMetric(fontinfo, glist[i], tgi); width = tgi->width; height = tgi->height; if ((height > 0) && (width > 0)) { for (j = i; j < txf->num_glyphs;) { if ((height > 0) && (width > 0)) { if (px + width + gap < texw) { foundWidthFit = 1; if (j != i) { i--; /* Step back so i loop increment leaves us at same character. */ } break; } } do { j++; } while (glist[j] == 0); if (j < txf->num_glyphs) { tgi = &txf->tgi[j]; getMetric(fontinfo, glist[j], tgi); width = tgi->width; height = tgi->height; } } /* If a fit was found, use that character; otherwise, advance a line in the texture. */ if (foundWidthFit) { if (height > maxheight) { maxheight = height; } c = j; } else { tgi = &txf->tgi[i]; getMetric(fontinfo, glist[i], tgi); width = tgi->width; height = tgi->height; py += maxheight + gap; px = gap; maxheight = height; if (py + height + gap >= texh) { if (texh*2 < max_tex_size[0]) { texh *= 2; ZnFree(txf->teximage); txf->teximage = ZnMalloc(texw * texh * sizeof(unsigned char)); strcpy(glist, glist2); goto restart; } else if (texw*2 < max_tex_size[0]) { texw *= 2; ZnFree(txf->teximage); txf->teximage = ZnMalloc(texw * texh * sizeof(unsigned char)); strcpy(glist, glist2); goto restart; } else { /* Overflowed texture space */ ZnWarning("Font texture overflow\n"); goto error; } } c = i; } /* Place the glyph in the texture image. */ placeGlyph(fontinfo, glist[c], txf->teximage, texw, px, py); /* Assign glyph's texture coordinate. */ tgi->x = px; tgi->y = py; /* Advance by glyph width, remaining in the current line. */ px += width + gap; } else { /* No texture image; assign invalid bogus texture coordinates. */ tgi->x = -1; tgi->y = -1; c = i; } glist[c] = 0; /* Mark processed; don't process again. */ txf->tgvi[c].t0[0] = tgi->x / texw + xstep; txf->tgvi[c].t0[1] = tgi->y / texh + ystep; txf->tgvi[c].v0[0] = tgi->xoffset; txf->tgvi[c].v0[1] = tgi->yoffset - tgi->height; txf->tgvi[c].t1[0] = (tgi->x + tgi->width) / texw + xstep; txf->tgvi[c].t1[1] = tgi->y / texh + ystep; txf->tgvi[c].v1[0] = (tgi->xoffset + tgi->width); txf->tgvi[c].v1[1] = tgi->yoffset - tgi->height; txf->tgvi[c].t2[0] = (tgi->x + tgi->width) / texw + xstep; txf->tgvi[c].t2[1] = (tgi->y + tgi->height) / texh + ystep; txf->tgvi[c].v2[0] = (tgi->xoffset + tgi->width); txf->tgvi[c].v2[1] = tgi->yoffset; txf->tgvi[c].t3[0] = tgi->x / texw + xstep; txf->tgvi[c].t3[1] = (tgi->y + tgi->height) / texh + ystep; txf->tgvi[c].v3[0] = tgi->xoffset; txf->tgvi[c].v3[1] = tgi->yoffset; txf->tgvi[c].advance = tgi->advance; } } min_glyph = txf->tgi[0].c; max_glyph = txf->tgi[0].c; for (i = 1; i < txf->num_glyphs; i++) { if (txf->tgi[i].c < min_glyph) { min_glyph = txf->tgi[i].c; } if (txf->tgi[i].c > max_glyph) { max_glyph = txf->tgi[i].c; } } txf->tex_width = texw; txf->tex_height = texh; /*printf("texture width: %g, texture height: %g\n", texw, texh); printf("min glyph: (%d) \"%c\", max glyph: (%d) \"%c\"\n", min_glyph, min_glyph, max_glyph, max_glyph);*/ txf->min_glyph = min_glyph; txf->range = max_glyph - min_glyph + 1; txf->lut = ZnMalloc(txf->range * sizeof(ZnTexGVI *)); if (txf->lut == NULL) { goto error; } memset(txf->lut, 0, txf->range * sizeof(ZnTexGVI *)); for (i = 0; i < txf->num_glyphs; i++) { txf->lut[txf->tgi[i].c - txf->min_glyph] = &txf->tgvi[i]; } for (i = 0; i < fontinfo->num_glyphs; i++) { if (fontinfo->glyph[i].bitmap) ZnFree(fontinfo->glyph[i].bitmap); } ZnFree(fontinfo); ZnFree(glist); ZnFree(glist2); } if (!this->texobj) { glGenTextures(1, &this->texobj); /*printf("%d creation texture %d pour la fonte %s\n", this->wi, 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY4, txf->tex_width, txf->tex_height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, txf->teximage); glBindTexture(GL_TEXTURE_2D, 0); } /*printf("%d utilisation de la texture %d\n", this->wi, this->texobj);*/ return this->texobj; error: if (glist) { ZnFree(glist); } if (glist2) { ZnFree(glist2); } if (fontinfo) { for (i = 0; i < fontinfo->num_glyphs; i++) { if (fontinfo->glyph[i].bitmap) ZnFree(fontinfo->glyph[i].bitmap); } ZnFree(fontinfo); } if (txf->tgi) { ZnFree(txf->tgi); txf->tgi = NULL; } if (txf->tgvi) { ZnFree(txf->tgvi); txf->tgvi = NULL; } if (txf->lut) { ZnFree(txf->lut); txf->lut = NULL; } if (txf->teximage) { ZnFree(txf->teximage); txf->teximage = NULL; } return 0; } /* ********************************************************************************** * * ZnFreeTexFont -- * ********************************************************************************** */ void ZnFreeTexFont(ZnTexFontInfo tfi) { TexFontInfo *this = ((TexFontInfo *) tfi); WidgetInfo *wi = this->wi; 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 && wi->win) { /*printf("%d Libération de la texture %d pour la fonte %s\n", wi, this->texobj, ZnNameOfTexFont(tfi));*/ glXMakeCurrent(wi->dpy, ZnWindowId(wi->win), wi->gl_context); glDeleteTextures(1, &this->texobj); } /* * There is no more client for this font * deallocate the structures. */ if (txf->tfi == NULL) { /*printf("%d destruction complète du txf pour %s\n", this->wi, ZnNameOfTexFont(tfi));*/ ZnFree(txf->tgi); ZnFree(txf->tgvi); ZnFree(txf->lut); ZnFree(txf->teximage); ZnFree(txf); Tcl_DeleteHashEntry(txf->hash); } ZnFree(this); } /* ********************************************************************************** * * ZnCharInTexFont -- * ********************************************************************************** */ ZnBool ZnCharInTexFont(ZnTexFontInfo tfi, int c) { TexFont *txf = ((TexFontInfo *) tfi)->txf; if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) { if (txf->lut[c - txf->min_glyph]) { return True; } } return False; } /* ********************************************************************************** * * ZnTexFontGVI -- * ********************************************************************************** */ ZnTexGVI * ZnTexFontGVI(ZnTexFontInfo tfi, int c) { TexFont *txf = ((TexFontInfo *) tfi)->txf; ZnTexGVI *tgvi; /* Automatically substitute uppercase letters with lowercase if not uppercase available (and vice versa). */ if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) { tgvi = txf->lut[c - txf->min_glyph]; if (tgvi) { return tgvi; } if (islower(c)) { c = toupper(c); if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) { return txf->lut[c - txf->min_glyph]; } } if (isupper(c)) { c = tolower(c); if ((c >= txf->min_glyph) && (c < txf->min_glyph + txf->range)) { return txf->lut[c - txf->min_glyph]; } } } fprintf(stderr, "Tried to access unavailable texture font character '%c'(\\0%o)\n", c, c); return txf->lut[(int)'!' - txf->min_glyph]; } #endif