--- /dev/null
+/*
+ * bltTile.c --
+ *
+ * This module manages images for tiled backgrounds for the BLT toolkit.
+ *
+ * Copyright 1995-1998 Lucent Technologies, Inc.
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that the above copyright notice appear in all
+ * copies and that both that the copyright notice and warranty
+ * disclaimer appear in supporting documentation, and that the names
+ * of Lucent Technologies any of their entities not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.
+ *
+ * Lucent Technologies disclaims all warranties with regard to this
+ * software, including all implied warranties of merchantability and
+ * fitness. In no event shall Lucent Technologies be liable for any
+ * special, indirect or consequential damages or any damages
+ * whatsoever resulting from loss of use, data or profits, whether in
+ * an action of contract, negligence or other tortuous action, arising
+ * out of or in connection with the use or performance of this
+ * software.
+ */
+
+#include "bltInt.h"
+#include "bltChain.h"
+#include "bltHash.h"
+#include "bltImage.h"
+#include <X11/Xutil.h>
+
+#include "bltTile.h"
+
+#define TILE_THREAD_KEY "BLT Tile Data"
+#define TILE_MAGIC ((unsigned int) 0x46170277)
+
+typedef struct {
+ Blt_HashTable tileTable; /* Hash table of tile structures keyed by
+ * the name of the image. */
+ Tcl_Interp *interp;
+} TileInterpData;
+
+typedef struct {
+ char *name; /* Name of image used to generate the pixmap.*/
+ Display *display; /* Display where pixmap was created. */
+ int flags; /* See definitions below. */
+ Tcl_Interp *interp;
+ Blt_HashEntry *hashPtr; /* Pointer to hash table location */
+ Blt_HashTable *tablePtr;
+
+ Pixmap pixmap; /* Pixmap generated from image. */
+ Pixmap mask; /* Monochrome pixmap used as
+ * transparency mask. */
+ GC gc; /* GC */
+ Tk_Image tkImage; /* Tk image token. */
+ Blt_Chain *clients; /* Chain of clients sharing this tile. */
+ int width, height;
+} Tile;
+
+#define NOTIFY_PENDING 1 /* If set, indicates that the image
+ * associated with the tile has been
+ * updated or deleted. The tile pixmap
+ * will be changed and the clients of the
+ * tile will be notified (if they supplied
+ * a TileChangedProc routine. */
+#define FIXED_TS_ORIGIN 2 /* TsOrigin is alway the widget. */
+#define TILE_ALPHA 4 /* Always fill with border color as image has an alpha. */
+
+typedef struct Blt_TileClientStruct {
+ unsigned int magic;
+ Tk_Window tkwin; /* Client window. */
+ int xOrigin, yOrigin; /* Tiling origin in relation to the
+ * client window. */
+ Blt_TileChangedProc *notifyProc; /* If non-NULL, routine to
+ * call to when tile image changes. */
+ ClientData clientData; /* Data to pass to when calling the above
+ * routine. */
+ Tile *tilePtr; /* Pointer to actual tile information */
+ Blt_ChainLink *linkPtr; /* Pointer to client entry in the server's
+ * client list. Used to delete the client */
+} TileClient;
+
+typedef struct {
+ Display *display;
+ Tk_Uid nameId;
+ int depth;
+} TileKey;
+
+static TileInterpData *GetTileInterpData _ANSI_ARGS_((Tcl_Interp *interp));
+
+static Tcl_IdleProc UpdateTile;
+static Tk_ImageChangedProc ImageChangedProc;
+static Tcl_InterpDeleteProc TileInterpDeleteProc;
+
+static void DestroyClient _ANSI_ARGS_((TileClient *clientPtr));
+static void DestroyTile _ANSI_ARGS_((Tile *tilePtr));
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RedrawTile --
+ *
+ * Generates a pixmap and draws the tile image into it. Also
+ * a tranparency mask is possibly generated from the image.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+RedrawTile(tkwin, tilePtr)
+ Tk_Window tkwin;
+ Tile *tilePtr;
+{
+ GC newGC;
+ Tk_PhotoHandle photo;
+ XGCValues gcValues;
+ int width, height;
+ unsigned int gcMask;
+
+ Tk_SizeOfImage(tilePtr->tkImage, &width, &height);
+ if (height<=0 || width<=0) return;
+
+ Tk_MakeWindowExist(tkwin);
+ if ((width != tilePtr->width) || (height != tilePtr->height)) {
+ Pixmap pixmap;
+
+ /*
+ * Create the new pixmap *before* destroying the old one. I don't
+ * why this happens, but if you delete the old pixmap first, the
+ * old pixmap sometimes gets used in the client's GCs. I suspect
+ * it has something to do with the way Tk reallocates X resource
+ * identifiers.
+ */
+ pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), width,
+ height, Tk_Depth(tkwin));
+ if (tilePtr->pixmap != None) {
+ Tk_FreePixmap(Tk_Display(tkwin), tilePtr->pixmap);
+ }
+ tilePtr->pixmap = pixmap;
+ }
+ Tk_RedrawImage(tilePtr->tkImage, 0, 0, width, height, tilePtr->pixmap,
+ 0, 0);
+
+ gcMask = (GCTile | GCFillStyle);
+ gcValues.fill_style = FillTiled;
+ gcValues.tile = tilePtr->pixmap;
+ newGC = Tk_GetGC(tkwin, gcMask, &gcValues);
+ if (tilePtr->gc != NULL) {
+ Tk_FreeGC(Tk_Display(tkwin), tilePtr->gc);
+ }
+ tilePtr->gc = newGC;
+ tilePtr->width = width;
+ tilePtr->height = height;
+
+ if (tilePtr->mask != None) {
+#ifdef WIN32
+ Tk_FreePixmap(Tk_Display(tkwin), tilePtr->mask);
+#else
+ XFreePixmap(Tk_Display(tkwin), tilePtr->mask);
+#endif /* WIN32 */
+ tilePtr->mask = None;
+ }
+ photo = Blt_FindPhoto(tilePtr->interp,
+ Blt_NameOfImage(tilePtr->tkImage));
+ if (photo != NULL) {
+ Tk_PhotoImageBlock src;
+
+ Tk_PhotoGetImage(photo, &src);
+ if ((src.offset[3] < src.pixelSize) && (src.offset[3] >= 0)) {
+ tilePtr->mask = Blt_PhotoImageMask(tkwin, src);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UpdateTile --
+ *
+ * It would be better if Tk checked for NULL proc pointers.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+UpdateTile(clientData)
+ ClientData clientData;
+{
+ Tile *tilePtr = (Tile *)clientData;
+ TileClient *clientPtr;
+ Blt_ChainLink *linkPtr;
+
+ tilePtr->flags &= ~NOTIFY_PENDING;
+ if (Tk_ImageIsDeleted(tilePtr->tkImage)) {
+ if (tilePtr->pixmap != None) {
+ Tk_FreePixmap(tilePtr->display, tilePtr->pixmap);
+ }
+ tilePtr->pixmap = None;
+ } else {
+ /* Pick any client window to generate the new pixmap. */
+ linkPtr = Blt_ChainFirstLink(tilePtr->clients);
+ clientPtr = Blt_ChainGetValue(linkPtr);
+ RedrawTile(clientPtr->tkwin, tilePtr);
+ }
+
+ /* Notify each of the tile's clients that the pixmap has changed. */
+
+ for (linkPtr = Blt_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
+ linkPtr = Blt_ChainNextLink(linkPtr)) {
+ clientPtr = Blt_ChainGetValue(linkPtr);
+ if (clientPtr->notifyProc != NULL) {
+ (*clientPtr->notifyProc) (clientPtr->clientData, clientPtr);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ImageChangedProc
+ *
+ * The Tk image has changed or been deleted, redraw the pixmap
+ * tile.
+ *
+ * Note: As of Tk 4.2 (rechecked in 8.3), if you redraw Tk
+ * images from a Tk_ImageChangedProc you'll get a
+ * coredump. As a workaround, we have to simulate
+ * how the Tk widgets use images and redraw within
+ * an idle event.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+/* ARGSUSED */
+static void
+ImageChangedProc(clientData, x, y, width, height, imageWidth, imageHeight)
+ ClientData clientData;
+ int x, y, width, height; /* Not used. */
+ int imageWidth, imageHeight; /* Not used. */
+{
+ Tile *tilePtr = (Tile *) clientData;
+
+ if (!(tilePtr->flags & NOTIFY_PENDING)) {
+ Tcl_DoWhenIdle(UpdateTile, tilePtr);
+ tilePtr->flags |= NOTIFY_PENDING;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DestroyTile --
+ *
+ * Deletes the core tile structure, including the pixmap
+ * representing the tile.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+DestroyTile(Tile *tilePtr)
+{
+ Blt_ChainLink *linkPtr;
+ TileClient *clientPtr;
+
+ if (tilePtr->flags & NOTIFY_PENDING) {
+ Tcl_CancelIdleCall(UpdateTile, tilePtr);
+ }
+ for (linkPtr = Blt_ChainFirstLink(tilePtr->clients); linkPtr != NULL;
+ linkPtr = Blt_ChainNextLink(linkPtr)) {
+ clientPtr = Blt_ChainGetValue(linkPtr);
+ Blt_Free(clientPtr);
+ }
+ Blt_ChainDestroy(tilePtr->clients);
+
+ if (tilePtr->hashPtr != NULL) {
+ Blt_DeleteHashEntry(tilePtr->tablePtr, tilePtr->hashPtr);
+ }
+ if (tilePtr->pixmap != None) {
+ Tk_FreePixmap(tilePtr->display, tilePtr->pixmap);
+ }
+ Tk_FreeImage(tilePtr->tkImage);
+
+ if (tilePtr->gc != NULL) {
+ Tk_FreeGC(tilePtr->display, tilePtr->gc);
+ }
+ if (tilePtr->name != NULL) {
+ Blt_Free(tilePtr->name);
+ }
+ Blt_Free(tilePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CreateTile --
+ *
+ * Creates a tile server. A tile server manages a single image,
+ * possibly shared by several clients. Clients will be updated
+ * (if requested) by the server if the image changes, so they
+ * know to redraw themselves. For X11 the image is drawn into a
+ * pixmap that is used in a new GC as its tile. For Windows we
+ * have to do the tiling ourselves by redrawing the image across
+ * the drawing area (see Blt_TileRectangle and Blt_TilePolygon).
+ *
+ * Results:
+ * Returns a pointer to the new tile server. If the image name
+ * does not represent a Tk image, NULL is returned.
+ *
+ *----------------------------------------------------------------------
+ */
+static Tile *
+CreateTile(
+ Tcl_Interp *interp,
+ Tk_Window tkwin,
+ char *imageName)
+{
+ Tile *tilePtr;
+ Tk_Image tkImage;
+
+ tilePtr = Blt_Calloc(1, sizeof(Tile));
+ assert(tilePtr);
+ /*
+ * Get the image. Funnel all change notifications to a single routine.
+ */
+ tkImage = Tk_GetImage(interp, tkwin, imageName, ImageChangedProc,
+ tilePtr);
+ if (tkImage == NULL) {
+ Blt_Free(tilePtr);
+ return NULL;
+ }
+
+ /*
+ * Initialize the tile server.
+ */
+ tilePtr->display = Tk_Display(tkwin);
+ tilePtr->interp = interp;
+ tilePtr->name = Blt_Strdup(imageName);
+ tilePtr->clients = Blt_ChainCreate();
+ tilePtr->tkImage = tkImage;
+ if (strchr(imageName, '!')) {
+ tilePtr->flags |= FIXED_TS_ORIGIN;
+ }
+ if (strchr(imageName, '|')) {
+ tilePtr->flags |= TILE_ALPHA;
+ }
+ RedrawTile(tkwin, tilePtr);
+ return tilePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DestroyClient --
+ *
+ * Removes the client from the servers's list of clients and
+ * memory used by the client token is released. When the last
+ * client is deleted, the server is also removed.
+ *
+ * Results:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+DestroyClient(TileClient *clientPtr)
+{
+ Tile *tilePtr;
+ tilePtr = clientPtr->tilePtr;
+
+ /* Remove the client from the server's list */
+ if (clientPtr->linkPtr != NULL) {
+ Blt_ChainDeleteLink(tilePtr->clients, clientPtr->linkPtr);
+ }
+ if (Blt_ChainGetLength(tilePtr->clients) == 0) {
+ /*
+ * If there are no more clients of the tile, then remove the
+ * pixmap, image, and the server record.
+ */
+ DestroyTile(tilePtr);
+ }
+ Blt_Free(clientPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CreateClient --
+ *
+ * Returns a token to a tile (possibly shared by many clients).
+ * A client uses the token to query or display the tile. Clients
+ * request tiles by their image names. Each tile is known by its
+ * display, screen depth, and image name. The tile server tracks
+ * what clients are using the tile and notifies them (via a
+ * callback) whenever the tile changes. If no server exists
+ * already, one is created on-the-fly.
+ *
+ * Results:
+ * A pointer to the newly created client (i.e. tile).
+ *
+ *----------------------------------------------------------------------
+ */
+static TileClient *
+CreateClient(
+ Tcl_Interp *interp,
+ Tk_Window tkwin,
+ char *name)
+{
+ TileClient *clientPtr;
+ Tile *tilePtr;
+ TileInterpData *dataPtr;
+ Blt_HashEntry *hPtr;
+ int isNew;
+ TileKey key;
+
+ dataPtr = GetTileInterpData(interp);
+
+ key.nameId = Tk_GetUid(name);
+ key.display = Tk_Display(tkwin);
+ key.depth = Tk_Depth(tkwin);
+ hPtr = Blt_CreateHashEntry(&dataPtr->tileTable, (char *)&key, &isNew);
+ if (isNew) {
+ tilePtr = CreateTile(interp, tkwin, name);
+ if (tilePtr == NULL) {
+ Blt_DeleteHashEntry(&(dataPtr->tileTable), hPtr);
+ return NULL;
+ }
+ tilePtr->hashPtr = hPtr;
+ tilePtr->tablePtr = &(dataPtr->tileTable);
+ Blt_SetHashValue(hPtr, tilePtr);
+ } else {
+ tilePtr = Blt_GetHashValue(hPtr);
+ }
+ clientPtr = Blt_Calloc(1, sizeof(TileClient));
+ assert(clientPtr);
+
+ /* Initialize client information. */
+ clientPtr->magic = TILE_MAGIC;
+ clientPtr->tkwin = tkwin;
+ clientPtr->linkPtr = Blt_ChainAppend(tilePtr->clients, clientPtr);
+ clientPtr->tilePtr = tilePtr;
+ return clientPtr;
+}
+
+/*
+ * -----------------------------------------------------------------------
+ *
+ * TileInterpDeleteProc --
+ *
+ * This is called when the interpreter is deleted. All the tiles
+ * are specific to that interpreter are destroyed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Destroys the tile table.
+ *
+ * ------------------------------------------------------------------------
+ */
+/* ARGSUSED */
+static void
+TileInterpDeleteProc(
+ ClientData clientData, /* Thread-specific data. */
+ Tcl_Interp *interp)
+{
+ TileInterpData *dataPtr = clientData;
+ Blt_HashEntry *hPtr;
+ Blt_HashSearch cursor;
+ Tile *tilePtr;
+
+ for (hPtr = Blt_FirstHashEntry(&(dataPtr->tileTable), &cursor);
+ hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
+ tilePtr = Blt_GetHashValue(hPtr);
+ tilePtr->hashPtr = NULL;
+ DestroyTile(tilePtr);
+ }
+ Blt_DeleteHashTable(&(dataPtr->tileTable));
+ Tcl_DeleteAssocData(interp, TILE_THREAD_KEY);
+ Blt_Free(dataPtr);
+}
+
+static TileInterpData *
+GetTileInterpData(interp)
+ Tcl_Interp *interp;
+{
+ TileInterpData *dataPtr;
+ Tcl_InterpDeleteProc *proc;
+
+ dataPtr = (TileInterpData *)
+ Tcl_GetAssocData(interp, TILE_THREAD_KEY, &proc);
+ if (dataPtr == NULL) {
+ dataPtr = Blt_Malloc(sizeof(TileInterpData));
+ assert(dataPtr);
+ dataPtr->interp = interp;
+ Tcl_SetAssocData(interp, TILE_THREAD_KEY, TileInterpDeleteProc,
+ dataPtr);
+ Blt_InitHashTable(&(dataPtr->tileTable), sizeof(TileKey)/sizeof(int));
+ }
+ return dataPtr;
+}
+
+\f
+/* Public API for tiles. */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_GetTile
+ *
+ * Convert the named image into a tile.
+ *
+ * Results:
+ * If the image is valid, a new tile is returned. If the name
+ * does not represent a proper image, an error message is left in
+ * interp->result.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+int
+Blt_GetTile(
+ Tcl_Interp *interp, /* Interpreter to report results back to */
+ Tk_Window tkwin, /* Window on the same display as tile */
+ char *imageName, /* Name of image */
+ Blt_Tile *tokenPtr) /* (out) Returns the allocated tile token. */
+{
+ TileClient *clientPtr;
+
+ clientPtr = CreateClient(interp, tkwin, imageName);
+ if (clientPtr == NULL) {
+ return TCL_ERROR;
+ }
+ *tokenPtr = clientPtr;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_FreeTile
+ *
+ * Release the resources associated with the tile.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory and X resources are freed. Bookkeeping information
+ * about the tile (i.e. width, height, and name) is discarded.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+void
+Blt_FreeTile(TileClient *clientPtr) /* Tile to be deleted */
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ return; /* No tile */
+ }
+ DestroyClient(clientPtr);
+}
+
+void
+Blt_FixedTile(TileClient *clientPtr)
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ return; /* No tile */
+ }
+ clientPtr->tilePtr->flags |= FIXED_TS_ORIGIN;
+}
+
+int
+Blt_TileFlags(TileClient *clientPtr)
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ return 0; /* No tile */
+ }
+ return clientPtr->tilePtr->flags;
+}
+
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_NameOfTile
+ *
+ * Returns the name of the image from which the tile was
+ * generated.
+ *
+ * Results:
+ * The name of the image is returned. The name is not unique.
+ * Many tiles may use the same image.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+char *
+Blt_NameOfTile(TileClient *clientPtr) /* Tile to query */
+{
+ if (clientPtr == NULL) {
+ return "";
+ }
+ if (clientPtr->magic != TILE_MAGIC) {
+ return "not a tile";
+ }
+ return clientPtr->tilePtr->name;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_PixmapOfTile
+ *
+ * Returns the pixmap of the tile.
+ *
+ * Results:
+ * The X pixmap used as the tile is returned.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+Pixmap
+Blt_PixmapOfTile(TileClient *clientPtr) /* Tile to query */
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ return None;
+ }
+ return clientPtr->tilePtr->pixmap;
+}
+
+/* Return 1 if has a non-zero size tile. */
+int
+Blt_HasTile( TileClient *clientPtr)
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ return 0;
+ }
+ if (clientPtr->tilePtr->width <= 1) return 0;
+ if (clientPtr->tilePtr->height <= 1) return 0;
+ return 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_SizeOfTile
+ *
+ * Returns the width and height of the tile.
+ *
+ * Results:
+ * The width and height of the tile are returned.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+void
+Blt_SizeOfTile(
+ TileClient *clientPtr, /* Tile to query */
+ int *widthPtr,
+ int *heightPtr) /* Returned dimensions of the tile (out) */
+{
+ if ((clientPtr == NULL) || (clientPtr->magic != TILE_MAGIC)) {
+ *widthPtr = *heightPtr = 0;
+ return; /* No tile given. */
+ }
+ *widthPtr = clientPtr->tilePtr->width;
+ *heightPtr = clientPtr->tilePtr->height;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_SetTileChangedProc
+ *
+ * Sets the routine to called when an image changes.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The designated routine will be called the next time the
+ * image associated with the tile changes.
+ *
+ *----------------------------------------------------------------------
+ */
+/*LINTLIBRARY*/
+void
+Blt_SetTileChangedProc(
+ TileClient *clientPtr, /* Tile to query */
+ Blt_TileChangedProc *notifyProc,
+ ClientData clientData)
+{
+ if ((clientPtr != NULL) && (clientPtr->magic == TILE_MAGIC)) {
+ clientPtr->notifyProc = notifyProc;
+ clientPtr->clientData = clientData;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_SetTileOrigin --
+ *
+ * Set the pattern origin of the tile to a common point (i.e. the
+ * origin (0,0) of the top level window) so that tiles from two
+ * different widgets will match up. This done by setting the
+ * GCTileStipOrigin field is set to the translated origin of the
+ * toplevel window in the hierarchy.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The GCTileStipOrigin is reset in the GC. This will cause the
+ * tile origin to change when the GC is used for drawing.
+ *
+ *----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+void
+Blt_SetTileOrigin(
+ Tk_Window tkwin,
+ TileClient *clientPtr,
+ int x, int y)
+{
+ while (!Tk_IsTopLevel(tkwin)) {
+ if (Tk_IsBgTileTop(tkwin)) break;
+ x += Tk_X(tkwin) + Tk_Changes(tkwin)->border_width;
+ y += Tk_Y(tkwin) + Tk_Changes(tkwin)->border_width;
+ tkwin = Tk_Parent(tkwin);
+ }
+ if (clientPtr->tilePtr->gc) {
+ XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, -x, -y);
+ }
+ clientPtr->xOrigin = -x;
+ clientPtr->yOrigin = -y;
+}
+
+void
+Blt_SetTSOrigin(
+ Tk_Window tkwin,
+ TileClient *clientPtr,
+ int x, int y)
+{
+ if (clientPtr->tilePtr->gc) {
+ XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, x, y);
+ }
+ clientPtr->xOrigin = x;
+ clientPtr->yOrigin = y;
+}
+
+#ifdef WIN32
+static int tkpWinRopModes[] =
+{
+ R2_BLACK, /* GXclear */
+ R2_MASKPEN, /* GXand */
+ R2_MASKPENNOT, /* GXandReverse */
+ R2_COPYPEN, /* GXcopy */
+ R2_MASKNOTPEN, /* GXandInverted */
+ R2_NOT, /* GXnoop */
+ R2_XORPEN, /* GXxor */
+ R2_MERGEPEN, /* GXor */
+ R2_NOTMERGEPEN, /* GXnor */
+ R2_NOTXORPEN, /* GXequiv */
+ R2_NOT, /* GXinvert */
+ R2_MERGEPENNOT, /* GXorReverse */
+ R2_NOTCOPYPEN, /* GXcopyInverted */
+ R2_MERGENOTPEN, /* GXorInverted */
+ R2_NOTMASKPEN, /* GXnand */
+ R2_WHITE /* GXset */
+};
+#define MASKPAT 0x00E20746 /* dest = (src & pat) | (!src & dst) */
+#define COPYFG 0x00CA0749 /* dest = (pat & src) | (!pat & dst) */
+#define COPYBG 0x00AC0744 /* dest = (!pat & src) | (pat & dst) */
+
+static void
+TileRegion(
+ HDC srcDC, /* Source device context. */
+ HDC destDC, /* Destination device context. */
+ HDC maskDC, /* If non-NULL, device context of the
+ * mask tile mask. */
+ TileClient *clientPtr,
+ int x, int y,
+ int width, int height)
+{
+ Tile *tilePtr = clientPtr->tilePtr;
+ int destX, destY;
+ int destWidth, destHeight;
+ int srcX, srcY;
+ int startX, startY; /* Starting upper left corner of region. */
+ int delta;
+ int left, top, right, bottom;
+
+ startX = x;
+ if (x < clientPtr->xOrigin) {
+ delta = (clientPtr->xOrigin - x) % tilePtr->width;
+ if (delta > 0) {
+ startX -= (tilePtr->width - delta);
+ }
+ } else if (x > clientPtr->xOrigin) {
+ delta = (x - clientPtr->xOrigin) % tilePtr->width;
+ if (delta > 0) {
+ startX -= delta;
+ }
+ }
+ startY = y;
+ if (y < clientPtr->yOrigin) {
+ delta = (clientPtr->yOrigin - y) % tilePtr->height;
+ if (delta > 0) {
+ startY -= (tilePtr->height - delta);
+ }
+ } else if (y >= clientPtr->yOrigin) {
+ delta = (y - clientPtr->yOrigin) % tilePtr->height;
+ if (delta > 0) {
+ startY -= delta;
+ }
+ }
+#ifdef notdef
+ PurifyPrintf("tile is (%d,%d,%d,%d)\n",
+ clientPtr->xOrigin, clientPtr->yOrigin,
+ tilePtr->width, tilePtr->height);
+ PurifyPrintf("region is (%d,%d,%d,%d)\n", x, y, width, height);
+ PurifyPrintf("starting at %d,%d\n", startX, startY);
+#endif
+ left = x;
+ right = x + width;
+ top = y;
+ bottom = y + height;
+ for (y = startY; y < bottom; y += tilePtr->height) {
+ srcY = 0;
+ destY = y;
+ destHeight = tilePtr->height;
+ if (y < top) {
+ srcY = (top - y);
+ destHeight = tilePtr->height - srcY;
+ destY = top;
+ }
+ if ((destY + destHeight) > bottom) {
+ destHeight = (bottom - destY);
+ }
+ for (x = startX; x < right; x += tilePtr->width) {
+ srcX = 0;
+ destX = x;
+ destWidth = tilePtr->width;
+ if (x < left) {
+ srcX = (left - x);
+ destWidth = tilePtr->width - srcX;
+ destX = left;
+ }
+ if ((destX + destWidth) > right) {
+ destWidth = (right - destX);
+ }
+#ifdef notdef
+ PurifyPrintf("drawing pattern (%d,%d,%d,%d) at %d,%d\n",
+ srcX , srcY, destWidth, destHeight, destX, destY);
+#endif
+ if (tilePtr->mask != None) { /* With transparency. */
+#ifdef notdef
+ HDC maskDC;
+ TkWinDCState maskState;
+
+ maskDC = TkWinGetDrawableDC(tilePtr->display,
+ tilePtr->mask, &maskState);
+ SetBkColor(destDC, RGB(255, 255, 255));
+ SetTextColor(destDC, RGB(0, 0, 0));
+#endif
+ BitBlt(destDC, destX, destY, destWidth, destHeight, maskDC,
+ 0, 0, SRCAND);
+ BitBlt(destDC, destX, destY, destWidth, destHeight, srcDC,
+ srcX, srcY, SRCPAINT);
+#ifdef notdef
+ TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
+#endif
+ } else { /* Opaque tile. */
+ BitBlt(destDC, destX, destY, destWidth, destHeight,
+ srcDC, srcX, srcY, SRCCOPY);
+ }
+ }
+ }
+}
+
+void
+Blt_TilePolygon(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ XPoint pointArr[],
+ int nPoints)
+{
+ HBITMAP oldBitmap;
+ HDC hDC, memDC;
+ HRGN hRgn;
+ POINT *p, *winPts;
+ Region2D bbox;
+ Tile *tilePtr;
+ TkWinDCState state;
+ TkWinDrawable *twdPtr;
+ XPoint *endPtr, *pointPtr;
+ int fillMode;
+ int width, height;
+
+ if (drawable == None) {
+ return;
+ }
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+
+ /* Determine the bounding box of the polygon. */
+ bbox.left = bbox.right = pointArr[0].x;
+ bbox.top = bbox.bottom = pointArr[0].y;
+
+ endPtr = pointArr + nPoints;
+ for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
+ if (pointPtr->x < bbox.left) {
+ bbox.left = pointPtr->x;
+ }
+ if (pointPtr->x > bbox.right) {
+ bbox.right = pointPtr->x;
+ }
+ if (pointPtr->y < bbox.top) {
+ bbox.top = pointPtr->y;
+ }
+ if (pointPtr->y > bbox.bottom) {
+ bbox.bottom = pointPtr->y;
+ }
+ }
+ width = bbox.right - bbox.left + 1;
+ height = bbox.bottom - bbox.top + 1;
+
+
+ /* Allocate and fill an array of POINTS to create the polygon path. */
+ p = winPts = Blt_Malloc(sizeof(POINT) * nPoints);
+ for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
+ p->x = pointPtr->x - bbox.left;
+ p->y = pointPtr->y - bbox.top;
+ p++;
+ }
+
+ hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
+ SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);
+ fillMode = (tilePtr->gc->fill_rule == EvenOddRule) ? ALTERNATE : WINDING;
+ /* Use the polygon as a clip path. */
+ LPtoDP(hDC, winPts, nPoints);
+ hRgn = CreatePolygonRgn(winPts, nPoints, fillMode);
+ SelectClipRgn(hDC, hRgn);
+ OffsetClipRgn(hDC, bbox.left, bbox.top);
+ Blt_Free(winPts);
+
+ twdPtr = (TkWinDrawable *)tilePtr->pixmap;
+ memDC = CreateCompatibleDC(hDC);
+ oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);
+
+ if (tilePtr->flags & FIXED_TS_ORIGIN) {
+ Blt_SetTSOrigin(tkwin, clientPtr, Tk_X(tkwin), Tk_Y(tkwin));
+ }
+
+ /* Tile the bounding box. */
+ if (tilePtr->mask != None) {
+ TkWinDCState maskState;
+ HDC maskDC;
+
+ maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
+ &maskState);
+ SetBkColor(hDC, RGB(255, 255, 255));
+ SetTextColor(hDC, RGB(0, 0, 0));
+ TileRegion(memDC, hDC, maskDC, clientPtr, bbox.left, bbox.top, width,
+ height);
+ TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
+ } else {
+ TileRegion(memDC, hDC, NULL, clientPtr, bbox.left, bbox.top, width,
+ height);
+ }
+ SelectBitmap(memDC, oldBitmap);
+ DeleteDC(memDC);
+ SelectClipRgn(hDC, NULL);
+ DeleteRgn(hRgn);
+ TkWinReleaseDrawableDC(drawable, hDC, &state);
+}
+
+
+void
+Blt_TileRectangle(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ int x, int y,
+ unsigned int width,
+ unsigned int height)
+{
+ HBITMAP oldBitmap;
+ HDC hDC, memDC;
+ Tile *tilePtr;
+ TkWinDCState state;
+ TkWinDrawable *twdPtr;
+
+ if (drawable == None) {
+ return;
+ }
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+ if (tilePtr->flags & FIXED_TS_ORIGIN) {
+ Blt_SetTSOrigin(tkwin, clientPtr, x, y);
+ /* XSetTSOrigin(Tk_Display(tkwin), clientPtr->tilePtr->gc, x, y); */
+ }
+ hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
+ SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);
+
+ twdPtr = (TkWinDrawable *)tilePtr->pixmap;
+ memDC = CreateCompatibleDC(hDC);
+ oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);
+
+ /* Tile the bounding box. */
+ if (tilePtr->mask != None) {
+ TkWinDCState maskState;
+ HDC maskDC;
+
+ maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
+ &maskState);
+ SetBkColor(hDC, RGB(255, 255, 255));
+ SetTextColor(hDC, RGB(0, 0, 0));
+ TileRegion(memDC, hDC, maskDC, clientPtr, x, y, width, height);
+ TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
+ } else {
+ TileRegion(memDC, hDC, NULL, clientPtr, x, y, width, height);
+ }
+ SelectBitmap(memDC, oldBitmap);
+ DeleteDC(memDC);
+ TkWinReleaseDrawableDC(drawable, hDC, &state);
+}
+
+void
+Blt_TileRectangles(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ XRectangle rectArr[],
+ int nRectangles)
+{
+ HBITMAP oldBitmap;
+ HDC hDC, memDC;
+ Tile *tilePtr;
+ TkWinDCState state;
+ TkWinDrawable *twdPtr;
+ XRectangle *rectPtr, *endPtr;
+
+ if (drawable == None) {
+ return;
+ }
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+ hDC = TkWinGetDrawableDC(Tk_Display(tkwin), drawable, &state);
+ SetROP2(hDC, tkpWinRopModes[tilePtr->gc->function]);
+
+ twdPtr = (TkWinDrawable *)tilePtr->pixmap;
+ memDC = CreateCompatibleDC(hDC);
+ oldBitmap = SelectBitmap(memDC, twdPtr->bitmap.handle);
+
+ endPtr = rectArr + nRectangles;
+ /* Tile the bounding box. */
+ if (tilePtr->mask != None) {
+ TkWinDCState maskState;
+ HDC maskDC;
+
+ maskDC = TkWinGetDrawableDC(tilePtr->display, tilePtr->mask,
+ &maskState);
+ SetBkColor(hDC, RGB(255, 255, 255));
+ SetTextColor(hDC, RGB(0, 0, 0));
+ for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
+ TileRegion(memDC, hDC, maskDC, clientPtr, (int)rectPtr->x,
+ (int)rectPtr->y, (int)rectPtr->width, (int)rectPtr->height);
+ }
+ TkWinReleaseDrawableDC(tilePtr->mask, maskDC, &maskState);
+ } else {
+ for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
+ TileRegion(memDC, hDC, NULL, clientPtr, (int)rectPtr->x,
+ (int)rectPtr->y, (int)rectPtr->width, (int)rectPtr->height);
+ }
+ }
+ SelectBitmap(memDC, oldBitmap);
+ DeleteDC(memDC);
+ TkWinReleaseDrawableDC(drawable, hDC, &state);
+}
+
+#else
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RectangleMask --
+ *
+ * Creates a rectangular mask also stippled by the mask of the
+ * tile. This is used to draw the tiled polygon images with
+ * transparent areas.
+ *
+ * Results:
+ * A bitmap mask is returned.
+ *
+ *----------------------------------------------------------------------
+ */
+static Pixmap
+RectangleMask(display, drawable, x, y, width, height, mask, xOrigin, yOrigin)
+ Display *display;
+ Drawable drawable;
+ int x, y;
+ unsigned int width, height;
+ Pixmap mask;
+ int xOrigin, yOrigin;
+{
+ GC gc;
+ Pixmap bitmap;
+ XGCValues gcValues;
+ unsigned long gcMask;
+
+ bitmap = Tk_GetPixmap(display, drawable, width, height, 1);
+ gcMask = (GCForeground | GCBackground | GCFillStyle |
+ GCTileStipXOrigin | GCTileStipYOrigin | GCStipple);
+ gcValues.foreground = 0x1;
+ gcValues.background = 0x0;
+ gcValues.fill_style = FillOpaqueStippled;
+ gcValues.ts_x_origin = xOrigin - x;
+ gcValues.ts_y_origin = yOrigin - y;
+ gcValues.stipple = mask;
+ gc = XCreateGC(display, bitmap, gcMask, &gcValues);
+ XFillRectangle(display, bitmap, gc, 0, 0, width, height);
+ Blt_FreePrivateGC(display, gc);
+ return bitmap;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_TileRectangle --
+ *
+ * Draws a rectangle filled by a tiled image. This differs from
+ * the normal XFillRectangle call in that we also try to handle
+ * a transparency mask.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Draws the rectangle.
+ *
+ *----------------------------------------------------------------------
+ */
+void
+Blt_TileRectangle(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ int x, int y,
+ unsigned int width,
+ unsigned int height)
+{
+ Tile *tilePtr;
+ Display *display;
+
+ if (height==0 || width == 0) return;
+ display = Tk_Display(tkwin);
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+ if (tilePtr->flags & FIXED_TS_ORIGIN) {
+ XSetTSOrigin(display, clientPtr->tilePtr->gc, x, y);
+ }
+ if (clientPtr->tilePtr->mask != None) {
+ Pixmap mask;
+ int xm, ym;
+
+ xm = clientPtr->xOrigin;
+ ym = clientPtr->yOrigin;
+ mask = RectangleMask(display, drawable, x, y, width, height,
+ tilePtr->mask, clientPtr->xOrigin, clientPtr->yOrigin);
+ XSetClipMask(display, tilePtr->gc, mask);
+ XSetClipOrigin(display, tilePtr->gc, x, y);
+ XFillRectangle(display, drawable, tilePtr->gc, x, y, width, height);
+ XSetClipMask(display, tilePtr->gc, None);
+ XSetClipOrigin(display, tilePtr->gc, 0, 0);
+ Tk_FreePixmap(display, mask);
+ } else {
+ XFillRectangle(display, drawable, tilePtr->gc, x, y, width, height);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_TileRectangles --
+ *
+ * Draws rectangles filled by a tiled image. This differs from
+ * the normal XFillRectangles call in that we also try to handle
+ * a transparency mask.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Draws the given rectangles.
+ *
+ *----------------------------------------------------------------------
+ */
+void
+Blt_TileRectangles(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ XRectangle rectArr[],
+ int nRectangles)
+{
+ Tile *tilePtr;
+
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+ if (tilePtr->mask != None) {
+ XRectangle *rectPtr, *endPtr;
+
+ endPtr = rectArr + nRectangles;
+ for (rectPtr = rectArr; rectPtr < endPtr; rectPtr++) {
+ Blt_TileRectangle(tkwin, drawable, clientPtr, rectPtr->x,
+ rectPtr->y, rectPtr->width, rectPtr->height);
+ }
+ } else {
+ XFillRectangles(Tk_Display(tkwin), drawable, tilePtr->gc, rectArr,
+ nRectangles);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * PolygonMask --
+ *
+ * Creates a polygon shaped mask also stippled by the mask
+ * of the tile. This is used to draw the tiled polygon images
+ * with transparent areas.
+ *
+ * Results:
+ * A bitmap mask is returned.
+ *
+ *----------------------------------------------------------------------
+ */
+static Pixmap
+PolygonMask(display, pointArr, nPoints, regionPtr, mask, xOrigin, yOrigin)
+ Display *display;
+ XPoint *pointArr;
+ int nPoints;
+ Region2D *regionPtr;
+ Pixmap mask;
+ int xOrigin, yOrigin;
+{
+ unsigned int width, height;
+ Pixmap bitmap;
+ GC gc;
+ XPoint *destArr;
+ register XPoint *srcPtr, *destPtr, *endPtr;
+
+ width = regionPtr->right - regionPtr->left + 1;
+ height = regionPtr->bottom - regionPtr->top + 1;
+ bitmap =
+ Tk_GetPixmap(display, DefaultRootWindow(display), width, height, 1);
+
+ destArr = Blt_Malloc(sizeof(XPoint) * nPoints);
+ endPtr = destArr + nPoints;
+ srcPtr = pointArr;
+ for (destPtr = destArr; destPtr < endPtr; destPtr++) {
+ destPtr->x = srcPtr->x - regionPtr->left;
+ destPtr->y = srcPtr->y - regionPtr->top;
+ srcPtr++;
+ }
+ gc = XCreateGC(display, bitmap, 0, NULL);
+ XFillRectangle(display, bitmap, gc, 0, 0, width, height);
+ XSetForeground(display, gc, 0x01);
+ XSetFillStyle(display, gc, FillStippled);
+ XSetTSOrigin(display, gc, xOrigin - regionPtr->left,
+ yOrigin - regionPtr->top);
+ XSetStipple(display, gc, mask);
+ XFillPolygon(display, bitmap, gc, destArr, nPoints, Complex,
+ CoordModeOrigin);
+ XFreeGC(display, gc);
+ Blt_Free(destArr);
+ return bitmap;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Blt_TilePolygon --
+ *
+ * Draws a polygon filled by a tiled image. This differs from
+ * the normal XFillPolygon call in that we also try to handle
+ * a transparency mask.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Draws the polygon.
+ *
+ *----------------------------------------------------------------------
+ */
+void
+Blt_TilePolygon(
+ Tk_Window tkwin,
+ Drawable drawable,
+ TileClient *clientPtr,
+ XPoint pointArr[],
+ int nPoints)
+{
+ Tile *tilePtr;
+ Display *display;
+
+ display = Tk_Display(tkwin);
+ tilePtr = clientPtr->tilePtr;
+ if (tilePtr->gc == NULL) return;
+ if (tilePtr->mask != None) {
+ XPoint *pointPtr, *endPtr;
+ Region2D region;
+ Pixmap mask;
+
+ if (tilePtr->flags & FIXED_TS_ORIGIN) {
+ Blt_SetTSOrigin(tkwin, clientPtr, Tk_X(tkwin), Tk_Y(tkwin));
+ }
+ /* Determine the bounding box of the polygon. */
+ pointPtr = pointArr;
+ region.left = region.right = pointPtr->x;
+ region.top = region.bottom = pointPtr->y;
+
+ endPtr = pointArr + nPoints;
+ for (pointPtr = pointArr; pointPtr < endPtr; pointPtr++) {
+ if (region.left > pointPtr->x) {
+ region.left = pointPtr->x;
+ } else if (region.right < pointPtr->x) {
+ region.right = pointPtr->x;
+ }
+ if (region.top > pointPtr->y) {
+ region.top = pointPtr->y;
+ } else if (region.bottom < pointPtr->y) {
+ region.bottom = pointPtr->y;
+ }
+ }
+ mask = PolygonMask(display, pointArr, nPoints, ®ion,
+ tilePtr->mask, clientPtr->xOrigin, clientPtr->yOrigin);
+ XSetClipMask(display, tilePtr->gc, mask);
+ XSetClipOrigin(display, tilePtr->gc, region.left, region.top);
+ XFillPolygon(display, drawable, tilePtr->gc, pointArr,
+ nPoints, Complex, CoordModeOrigin);
+ XSetClipMask(display, tilePtr->gc, None);
+ XSetClipOrigin(display, tilePtr->gc, 0, 0);
+ Tk_FreePixmap(display, mask);
+ } else {
+ XFillPolygon(display, drawable, tilePtr->gc, pointArr,
+ nPoints, Complex, CoordModeOrigin);
+ }
+}
+#endif
+
+void
+Blt_Fill3DRectangleTile(tkwin, drawable, border, x, y, width, height, borderWidth,
+ relief, tilePtr, scrollTile, flags)
+ Tk_Window tkwin; /* Window for which border was allocated. */
+ Drawable drawable; /* X window or pixmap in which to draw. */
+ Tk_3DBorder border; /* Token for border to draw. */
+ int x, y, width, height; /* Outside area of rectangular region. */
+ int borderWidth; /* Desired width for border, in
+ * pixels. Border will be *inside* region. */
+ int relief; /* Indicates 3D effect: TK_RELIEF_FLAT,
+ * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */
+ TileClient *tilePtr;
+ int scrollTile;
+ int flags;
+{
+
+ if (Blt_HasTile(tilePtr)==0 || (flags&1)) {
+ Blt_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, borderWidth, relief);
+ return;
+ }
+ if (border && (tilePtr->tilePtr->flags & TILE_ALPHA)) {
+ Blt_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, borderWidth, relief);
+ }
+ Blt_SetTileOrigin(tkwin, tilePtr, x, 0);
+ if (scrollTile) {
+ Blt_SetTSOrigin(tkwin, tilePtr, x, y);
+ } else {
+ Blt_SetTileOrigin(tkwin, tilePtr, x, 0);
+ }
+ Blt_TileRectangle(tkwin, drawable, tilePtr, x, y, width, height);
+ Blt_Draw3DRectangle(tkwin, drawable, border, x, y, width, height,
+ borderWidth, relief);
+}