Attachment "tk-9.0.3-ttk-image-el-cache.diff" to
ticket [7caf9e9e]
added by
mtmcp_
2026-06-09 16:07:50.
Index: generic/tkPort.h
==================================================================
--- A/generic/tkPort.h
+++ B/generic/tkPort.h
@@ -12,20 +12,17 @@
*/
#ifndef _TKPORT
#define _TKPORT
-#if defined(_WIN32)
-# include "tkWinPort.h"
-#endif
#ifndef _TK
# include "tk.h"
#endif
-#if !defined(_WIN32)
-# if defined(MAC_OSX_TK)
-# include "tkMacOSXPort.h"
-# else
-# include "tkUnixPort.h"
-# endif
+#if defined(_WIN32)
+# include "tkWinPort.h"
+#elif defined(MAC_OSX_TK)
+# include "tkMacOSXPort.h"
+#else
+# include "tkUnixPort.h"
#endif
#endif /* _TKPORT */
Index: generic/ttk/ttkImage.c
==================================================================
--- A/generic/ttk/ttkImage.c
+++ B/generic/ttk/ttkImage.c
@@ -9,23 +9,25 @@
* list is a sequence of statespec/imagename options as per
* [style map].
*/
#include "tkInt.h"
-#include "ttkTheme.h"
+#include "ttkThemeInt.h"
#define MIN(a,b) ((a) < (b) ? (a) : (b))
/*------------------------------------------------------------------------
* +++ ImageSpec management.
*/
struct TtkImageSpec {
Tk_Image baseImage; /* Base image to use */
+ Tcl_Obj *baseName; /* Name of base image */
int mapCount; /* #state-specific overrides */
Ttk_StateSpec *states; /* array[mapCount] of states ... */
Tk_Image *images; /* ... per-state images to use */
+ Tcl_Obj **names; /* array[mapCount] of image names */
Tk_ImageChangedProc *imageChanged;
void *imageChangedClientData;
};
/* NullImageChanged --
@@ -83,13 +85,15 @@
Tcl_Size objc;
Tcl_Obj **objv;
imageSpec = (Ttk_ImageSpec *)ckalloc(sizeof(*imageSpec));
imageSpec->baseImage = 0;
+ imageSpec->baseName = 0;
imageSpec->mapCount = 0;
imageSpec->states = 0;
imageSpec->images = 0;
+ imageSpec->names = 0;
imageSpec->imageChanged = imageChangedProc;
imageSpec->imageChangedClientData = imageChangedClientData;
if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
goto error;
@@ -106,18 +110,21 @@
}
n = (objc - 1) / 2;
imageSpec->states = (Ttk_StateSpec *)ckalloc(n * sizeof(Ttk_StateSpec));
imageSpec->images = (Tk_Image *)ckalloc(n * sizeof(Tk_Image));
+ imageSpec->names = (Tcl_Obj **)ckalloc(n * sizeof(Tcl_Obj *));
/* Get base image:
*/
imageSpec->baseImage = Tk_GetImage(
interp, tkwin, Tcl_GetString(objv[0]), ImageSpecImageChanged, imageSpec);
if (!imageSpec->baseImage) {
goto error;
}
+ imageSpec->baseName = objv[0];
+ Tcl_IncrRefCount(imageSpec->baseName);
/* Extract state and image specifications:
*/
for (i = 0; i < n; ++i) {
Tcl_Obj *stateSpec = objv[2*i + 1];
@@ -132,10 +139,12 @@
imageSpec->images[i] = Tk_GetImage(
interp, tkwin, imageName, NullImageChanged, NULL);
if (imageSpec->images[i] == NULL) {
goto error;
}
+ imageSpec->names[i] = objv[2*i + 2];
+ Tcl_IncrRefCount(imageSpec->names[i]);
imageSpec->mapCount = i+1;
}
return imageSpec;
@@ -151,15 +160,20 @@
{
int i;
for (i=0; i < imageSpec->mapCount; ++i) {
Tk_FreeImage(imageSpec->images[i]);
+ if (imageSpec->names && imageSpec->names[i]) {
+ Tcl_DecrRefCount(imageSpec->names[i]);
+ }
}
if (imageSpec->baseImage) { Tk_FreeImage(imageSpec->baseImage); }
+ if (imageSpec->baseName) { Tcl_DecrRefCount(imageSpec->baseName); }
if (imageSpec->states) { ckfree(imageSpec->states); }
if (imageSpec->images) { ckfree(imageSpec->images); }
+ if (imageSpec->names) { ckfree(imageSpec->names); }
ckfree(imageSpec);
}
/* TtkSelectImage --
@@ -177,10 +191,29 @@
}
}
return imageSpec->baseImage;
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+/* TtkSelectImageName --
+ * Return the name of the state-specific image selected by TtkSelectImage,
+ * so the corresponding photo can be looked up (for opacity testing).
+ */
+static Tcl_Obj *TtkSelectImageName(
+ Ttk_ImageSpec *imageSpec,
+ Ttk_State state)
+{
+ int i;
+ for (i = 0; i < imageSpec->mapCount; ++i) {
+ if (Ttk_StateMatches(state, imageSpec->states+i)) {
+ return imageSpec->names[i];
+ }
+ }
+ return imageSpec->baseName;
+}
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
/*------------------------------------------------------------------------
* +++ Drawing utilities.
*/
/* LPadding, CPadding, RPadding --
@@ -226,11 +259,11 @@
return;
}
for (x = dst.x; x < dr; x += src.width) {
int cw = MIN(src.width, dr - x);
- for (y = dst.y; y <= db; y += src.height) {
+ for (y = dst.y; y < db; y += src.height) {
int ch = MIN(src.height, db - y);
Tk_RedrawImage(image, src.x, src.y, cw, ch, d, x, y);
}
}
}
@@ -259,25 +292,49 @@
Ttk_Stripe(tkwin, d, image, BPadding(src,p), BPadding(dst,p), p);
}
/*------------------------------------------------------------------------
* +++ Image element definition.
+ *
+ * The image element is a pure renderer: ImageElementDraw tiles the selected
+ * image into whatever drawable it is handed. Caching of the composited result
+ * is owned by the layout-draw layer (the per-node cache in ttkLayout.c); this
+ * element only advertises that it is cacheable and reports its opacity and a
+ * content epoch via ImageElementCacheInfo.
*/
typedef struct { /* ClientData for image elements */
Ttk_ImageSpec *imageSpec; /* Image(s) to use */
int minWidth; /* Minimum width; overrides image width */
int minHeight; /* Minimum height; overrides image height */
Ttk_Sticky sticky; /* -stickiness specification */
Ttk_Padding border; /* Fixed border region */
Ttk_Padding padding; /* Internal padding */
+ unsigned epoch; /* Bumps when a source image's pixels change */
#ifdef TILE_07_COMPAT
Ttk_ResourceCache cache; /* Resource cache for images */
Ttk_StateMap imageMap; /* State-based lookup table for images */
#endif
} ImageData;
+
+/* ImageElementImageChanged --
+ * A source image's pixels changed; bump the content epoch so the
+ * layout-draw layer rebuilds any node pixmap caching this element.
+ */
+static void ImageElementImageChanged(
+ void *clientData,
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int))
+{
+ ImageData *imageData = (ImageData *)clientData;
+ imageData->epoch++;
+}
static void FreeImageData(void *clientData)
{
ImageData *imageData = (ImageData *)clientData;
if (imageData->imageSpec) { TtkFreeImageSpec(imageData->imageSpec); }
@@ -285,10 +342,82 @@
if (imageData->imageMap) { Tcl_DecrRefCount(imageData->imageMap); }
#endif
ckfree(clientData);
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+/* ImageIsOpaque --
+ * Return 1 if the named photo is fully opaque (every pixel alpha 255),
+ * so the element's render is background-independent and can be cached as
+ * a plain pixmap. A non-photo image, or one whose pixels cannot be read,
+ * is treated as translucent -- an always-correct (if unoptimized) answer.
+ */
+static int ImageIsOpaque(Tcl_Interp *interp, Tcl_Obj *imageName)
+{
+ Tk_PhotoHandle photo;
+ Tk_PhotoImageBlock block;
+ int x, y, alphaOff;
+
+ if (interp == NULL || imageName == NULL) {
+ return 0;
+ }
+ photo = Tk_FindPhoto(interp, Tcl_GetString(imageName));
+ if (photo == NULL || !Tk_PhotoGetImage(photo, &block)) {
+ return 0;
+ }
+ if (block.pixelSize < 4) {
+ return 1; /* No alpha channel: opaque. */
+ }
+ alphaOff = block.offset[3];
+ for (y = 0; y < block.height; y++) {
+ unsigned char *pixPtr = block.pixelPtr + y * block.pitch + alphaOff;
+ for (x = 0; x < block.width; x++) {
+ if (*pixPtr != 255) {
+ return 0;
+ }
+ pixPtr += block.pixelSize;
+ }
+ }
+ return 1;
+}
+
+/* ImageElementCacheInfo --
+ * Report the element's content epoch and whether its render fully and
+ * opaquely covers the parcel -- i.e. the state-selected image is opaque
+ * AND -sticky makes it fill the whole box. Only then is the render
+ * background-independent; a non-filling -sticky leaves margins that show
+ * the background, so such an element must be treated as translucent.
+ */
+static void ImageElementCacheInfo(
+ void *clientData,
+ TCL_UNUSED(void *), /* elementRecord */
+ Tk_Window tkwin,
+ Ttk_Box b,
+ Ttk_State state,
+ Ttk_ElementCacheInfo *info)
+{
+ ImageData *imageData = (ImageData *)clientData;
+ Tk_Image image = TtkSelectImage(imageData->imageSpec, tkwin, state);
+ int imgWidth, imgHeight;
+ Ttk_Box dst;
+
+ info->epoch = imageData->epoch;
+ info->opaque = 0;
+
+ if (image == NULL) {
+ return;
+ }
+ Tk_SizeOfImage(image, &imgWidth, &imgHeight);
+ dst = Ttk_StickBox(b, imgWidth, imgHeight, imageData->sticky);
+ if (dst.x == b.x && dst.y == b.y
+ && dst.width == b.width && dst.height == b.height) {
+ info->opaque = ImageIsOpaque(Tk_Interp(tkwin),
+ TtkSelectImageName(imageData->imageSpec, state));
+ }
+}
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
static void ImageElementSize(
void *clientData,
TCL_UNUSED(void *), /* elementRecord */
TCL_UNUSED(Tk_Window),
int *widthPtr,
@@ -373,10 +502,11 @@
{ "-border","-height","-padding","-sticky","-width",NULL };
enum { O_BORDER, O_HEIGHT, O_PADDING, O_STICKY, O_WIDTH };
Ttk_ImageSpec *imageSpec = 0;
ImageData *imageData = 0;
+ Ttk_ElementClass *elementClass;
int padding_specified = 0;
Tcl_Size i;
if (objc + 1 < 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
@@ -383,16 +513,20 @@
"Must supply a base image", -1));
Tcl_SetErrorCode(interp, "TTK", "IMAGE", "BASE", (char *)NULL);
return TCL_ERROR;
}
- imageSpec = TtkGetImageSpec(interp, Tk_MainWindow(interp), objv[0]);
+ imageData = (ImageData *)ckalloc(sizeof(*imageData));
+ memset(imageData, 0, sizeof(*imageData));
+
+ imageSpec = TtkGetImageSpecEx(interp, Tk_MainWindow(interp), objv[0],
+ ImageElementImageChanged, imageData);
if (!imageSpec) {
+ ckfree(imageData);
return TCL_ERROR;
}
- imageData = (ImageData *)ckalloc(sizeof(*imageData));
imageData->imageSpec = imageSpec;
imageData->minWidth = imageData->minHeight = -1;
imageData->sticky = TTK_FILL_BOTH;
imageData->border = imageData->padding = Ttk_UniformPadding(0);
#ifdef TILE_07_COMPAT
@@ -450,15 +584,19 @@
if (Ttk_GetStickyFromObj(interp, objv[i+1], &imageData->sticky)
!= TCL_OK) { goto error; }
}
}
- if (!Ttk_RegisterElement(interp, theme, elementName, &ImageElementSpec,
- imageData))
- {
+ elementClass = Ttk_RegisterElement(interp, theme, elementName,
+ &ImageElementSpec, imageData);
+ if (!elementClass) {
goto error;
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+ TtkSetElementCachePolicy(elementClass, TTK_ELEMENT_CACHEABLE,
+ ImageElementCacheInfo);
+#endif
Ttk_RegisterCleanup(interp, imageData, FreeImageData);
Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
return TCL_OK;
Index: generic/ttk/ttkLayout.c
==================================================================
--- A/generic/ttk/ttkLayout.c
+++ B/generic/ttk/ttkLayout.c
@@ -526,10 +526,11 @@
unsigned flags; /* Packing and sticky flags */
Ttk_ElementClass *eclass; /* Class record */
Ttk_State state; /* Current state */
Ttk_Box parcel; /* allocated parcel */
Ttk_LayoutNode *next, *child;
+ NodeDrawCache *drawCache; /* Per-node render cache (ttkNodeCache.c) */
};
static Ttk_LayoutNode *Ttk_NewLayoutNode(
unsigned flags, Ttk_ElementClass *elementClass)
{
@@ -538,19 +539,21 @@
node->flags = flags;
node->eclass = elementClass;
node->state = 0u;
node->next = node->child = 0;
node->parcel = Ttk_MakeBox(0,0,0,0);
+ node->drawCache = NULL;
return node;
}
static void Ttk_FreeLayoutNode(Ttk_LayoutNode *node)
{
while (node) {
Ttk_LayoutNode *next = node->next;
Ttk_FreeLayoutNode(node->child);
+ TtkFreeNodeDrawCache(node->drawCache);
ckfree(node);
node = next;
}
}
@@ -1118,13 +1121,21 @@
*/
/*
* Ttk_DrawLayout --
* Draw a layout tree.
+ *
+ * Each node's element is drawn through TtkDrawCachedElement (ttkNodeCache.c), which
+ * owns the per-node render cache: the composited output lives on the node (so it
+ * is per-widget-instance, with no cross-widget contamination) and the cache
+ * module -- which is handed the z-order context and the live background -- owns
+ * hit/miss and invalidation. Elements stay pure renderers; this file only walks
+ * the tree. Caching is a no-op on platforms without double buffering.
*/
+
static void Ttk_DrawNodeList(
- Ttk_Layout layout, Ttk_State state, Ttk_LayoutNode *node, Drawable d)
+ NodeDrawContext *ctx, Ttk_State state, Ttk_LayoutNode *node)
{
for (; node; node = node->next)
{
int border = node->flags & TTK_BORDER;
int substate = state;
@@ -1132,27 +1143,31 @@
if (node->flags & TTK_UNIT) {
substate |= node->state;
}
if (node->child && border) {
- Ttk_DrawNodeList(layout, substate, node->child, d);
+ Ttk_DrawNodeList(ctx, substate, node->child);
}
- Ttk_DrawElement(
- node->eclass,
- layout->style,layout->recordPtr,layout->optionTable,layout->tkwin,
- d, node->parcel, state | node->state);
+ TtkDrawCachedElement(ctx, node->eclass, &node->drawCache,
+ node->parcel, state | node->state);
if (node->child && !border) {
- Ttk_DrawNodeList(layout, substate, node->child, d);
+ Ttk_DrawNodeList(ctx, substate, node->child);
}
}
}
void Ttk_DrawLayout(Ttk_Layout layout, Ttk_State state, Drawable d)
{
- Ttk_DrawNodeList(layout, state, layout->root, d);
+ NodeDrawContext *ctx = TtkNodeDrawBegin(
+ layout->style, layout->recordPtr, layout->optionTable,
+ layout->tkwin, d);
+
+ Ttk_DrawNodeList(ctx, state, layout->root);
+
+ TtkNodeDrawEnd(ctx);
}
/*------------------------------------------------------------------------
* +++ Inquiry and modification.
*/
ADDED generic/ttk/ttkNodeCache.c
Index: generic/ttk/ttkNodeCache.c
==================================================================
--- A/generic/ttk/ttkNodeCache.c
+++ B/generic/ttk/ttkNodeCache.c
@@ -0,0 +1,286 @@
+/*
+ * ttkNodeCache.c --
+ *
+ * Per-node element *render* cache for the Ttk layout-draw traversal.
+ * (Distinct from ttkCache.c, which is the theme *resource* cache for
+ * fonts, colors and images.)
+ *
+ * The layout layer (ttkLayout.c) walks a widget's node tree in z-order and
+ * draws each node's element through this module. An element is a pure
+ * renderer: it draws into whatever drawable it is handed. This module
+ * caches the *composited* result of a cacheable element on its layout node
+ * -- so the cache is per-widget-instance, with no cross-widget
+ * contamination -- and replaces the per-tile redraw with a single blit on
+ * a cache hit.
+ *
+ * Because the cache is owned here, in the one place that sees both the
+ * z-order and the live background, invalidation needs no cooperation from
+ * widgets or elements: a transparent node is rebuilt whenever a lower node
+ * drawn this pass overlaps it (so a changed background shows through), and
+ * otherwise reused while its size, position, state and content epoch are
+ * unchanged. An opaque element that fully fills its parcel is
+ * background-independent and reused on size/position/state/epoch alone.
+ *
+ * The pixmap cache is compiled in only where XCopyArea accepts a Pixmap;
+ * on platforms without double buffering (e.g. macOS, which composites
+ * efficiently via Core Graphics) every entry point degrades to a direct
+ * element draw with no caching.
+ *
+ * Copyright © 2026 Tk contributors. Freely redistributable.
+ */
+
+#include "tkInt.h"
+#include "ttkThemeInt.h"
+
+/*
+ * NodeDrawContext --
+ * Per-draw-pass state, created by TtkNodeDrawBegin and torn down by
+ * TtkNodeDrawEnd. Carries the element-draw parameters pulled from the
+ * layout, the live destination, a shared GXcopy GC, and the set of
+ * parcels (re)drawn this pass (the dirty set driving invalidation).
+ */
+struct NodeDrawContext {
+ Ttk_Style style;
+ void *recordPtr;
+ Tk_OptionTable optionTable;
+ Tk_Window tkwin;
+ Drawable d;
+#ifndef TK_NO_DOUBLE_BUFFERING
+ Display *display;
+ GC copyGC; /* Lazily allocated GXcopy GC, or NULL */
+ Ttk_Box *dirty; /* Parcels (re)drawn fresh this pass */
+ int ndirty, dirtycap;
+#endif
+};
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+
+/*
+ * NodeDrawCache --
+ * One cached composited element, hung off a layout node. Holds either the
+ * bare opaque render or, for a translucent element, the element baked over
+ * the background it was last drawn against.
+ */
+struct NodeDrawCache {
+ Pixmap pixmap; /* Cached composited element, or None */
+ Display *display; /* Display owning pixmap (for Tk_FreePixmap) */
+ int x, y, w, h; /* Parcel the pixmap was built for */
+ Ttk_State state; /* State the pixmap was built for */
+ int opaque; /* 1: background-independent; 0: bg baked in */
+ unsigned epoch; /* Element content epoch when built */
+ int valid; /* Nonzero when the cache holds the key */
+};
+
+static int BoxesIntersect(Ttk_Box a, Ttk_Box b)
+{
+ return a.x < b.x + b.width && b.x < a.x + a.width
+ && a.y < b.y + b.height && b.y < a.y + a.height;
+}
+
+/* DirtyOverlaps, DirtyAdd --
+ * Track the parcels (re)drawn this pass. A transparent node whose parcel
+ * a dirty rect overlaps has a changed background and must rebuild; a node
+ * that draws marks its parcel dirty for the nodes layered above it.
+ */
+static int DirtyOverlaps(NodeDrawContext *ctx, Ttk_Box b)
+{
+ int i;
+ for (i = 0; i < ctx->ndirty; ++i) {
+ if (BoxesIntersect(ctx->dirty[i], b)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void DirtyAdd(NodeDrawContext *ctx, Ttk_Box b)
+{
+ if (ctx->ndirty >= ctx->dirtycap) {
+ ctx->dirtycap = ctx->dirtycap ? 2 * ctx->dirtycap : 8;
+ ctx->dirty = (Ttk_Box *)Tcl_Realloc(ctx->dirty,
+ ctx->dirtycap * sizeof(Ttk_Box));
+ }
+ ctx->dirty[ctx->ndirty++] = b;
+}
+
+static GC NodeCopyGC(NodeDrawContext *ctx)
+{
+ if (ctx->copyGC == NULL) {
+ XGCValues gcv;
+ gcv.function = GXcopy;
+ gcv.graphics_exposures = False;
+ ctx->copyGC = Tk_GetGC(ctx->tkwin,
+ GCFunction | GCGraphicsExposures, &gcv);
+ }
+ return ctx->copyGC;
+}
+
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
+/* TtkNodeDrawBegin --
+ * Open a draw pass for one layout tree. Captures the element-draw
+ * parameters and the destination; graphics state is allocated lazily.
+ */
+NodeDrawContext *TtkNodeDrawBegin(
+ Ttk_Style style,
+ void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tk_Window tkwin,
+ Drawable d)
+{
+ NodeDrawContext *ctx = (NodeDrawContext *)Tcl_Alloc(sizeof(*ctx));
+
+ ctx->style = style;
+ ctx->recordPtr = recordPtr;
+ ctx->optionTable = optionTable;
+ ctx->tkwin = tkwin;
+ ctx->d = d;
+#ifndef TK_NO_DOUBLE_BUFFERING
+ ctx->display = Tk_Display(tkwin);
+ ctx->copyGC = NULL;
+ ctx->dirty = NULL;
+ ctx->ndirty = ctx->dirtycap = 0;
+#endif
+ return ctx;
+}
+
+/* TtkNodeDrawEnd --
+ * Close a draw pass: release the shared GC and dirty set, then the
+ * context itself. The per-node pixmaps persist across passes and are
+ * freed with their nodes (TtkFreeNodeDrawCache).
+ */
+void TtkNodeDrawEnd(NodeDrawContext *ctx)
+{
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (ctx->copyGC != NULL) {
+ Tk_FreeGC(ctx->display, ctx->copyGC);
+ }
+ if (ctx->dirty != NULL) {
+ Tcl_Free(ctx->dirty);
+ }
+#endif
+ Tcl_Free(ctx);
+}
+
+/* TtkFreeNodeDrawCache --
+ * Release a node's cached pixmap and the cache record. NULL-tolerant;
+ * called by Ttk_FreeLayoutNode for every node.
+ */
+void TtkFreeNodeDrawCache(NodeDrawCache *cache)
+{
+ if (cache == NULL) {
+ return;
+ }
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (cache->pixmap != None) {
+ Tk_FreePixmap(cache->display, cache->pixmap);
+ }
+ Tcl_Free(cache);
+#endif
+}
+
+/* TtkDrawCachedElement --
+ * Draw one node's element, going through its per-node render cache. On a
+ * hit the cached composite is blitted in a single XCopyArea; on a miss the
+ * element is (re)rendered into the node pixmap -- seeded with the live
+ * background first unless it opaquely fills the parcel -- then stored and
+ * blitted. A draw marks the parcel dirty for nodes layered above it; a
+ * hit leaves it clean. Falls back to a direct element draw for
+ * non-cacheable elements, an unrealized window, or an allocation failure.
+ */
+void TtkDrawCachedElement(
+ NodeDrawContext *ctx,
+ Ttk_ElementClass *eclass,
+ NodeDrawCache **cacheSlot,
+ Ttk_Box b,
+ Ttk_State state)
+{
+ if (b.width <= 0 || b.height <= 0) {
+ return;
+ }
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (Ttk_ElementClassCacheable(eclass)
+ && Tk_WindowId(ctx->tkwin) != None) {
+ Ttk_ElementCacheInfo info;
+ NodeDrawCache *cache = *cacheSlot;
+ GC gc = NULL;
+
+ Ttk_ElementGetCacheInfo(eclass, ctx->style, ctx->recordPtr,
+ ctx->optionTable, ctx->tkwin, b, state, &info);
+
+ /*
+ * Fast path: blit the cached pixmap when size, position, state and
+ * content epoch match and -- for a translucent element -- nothing
+ * drawn beneath it changed this pass.
+ */
+ if (cache && cache->valid
+ && cache->x == b.x && cache->y == b.y
+ && cache->w == b.width && cache->h == b.height
+ && cache->state == state
+ && cache->opaque == info.opaque
+ && cache->epoch == info.epoch
+ && (info.opaque || !DirtyOverlaps(ctx, b))
+ && (gc = NodeCopyGC(ctx)) != NULL) {
+ XCopyArea(ctx->display, cache->pixmap, ctx->d, gc,
+ 0, 0, (unsigned)b.width, (unsigned)b.height, b.x, b.y);
+ return; /* clean: parcel not marked dirty */
+ }
+
+ /* Miss: (re)build the node pixmap. */
+ if (cache == NULL) {
+ cache = *cacheSlot = (NodeDrawCache *)Tcl_Alloc(sizeof(*cache));
+ memset(cache, 0, sizeof(*cache));
+ }
+ if (cache->pixmap != None
+ && (cache->w != b.width || cache->h != b.height)) {
+ Tk_FreePixmap(cache->display, cache->pixmap);
+ cache->pixmap = None;
+ }
+ if (cache->pixmap == None) {
+ cache->pixmap = Tk_GetPixmap(ctx->display, Tk_WindowId(ctx->tkwin),
+ b.width, b.height, Tk_Depth(ctx->tkwin));
+ cache->display = ctx->display;
+ }
+ gc = NodeCopyGC(ctx);
+ if (cache->pixmap != None && gc != NULL) {
+ if (!info.opaque) {
+ /* Seed with the live background so translucent pixels blend
+ * and any non-filling -sticky margins show through. */
+ XCopyArea(ctx->display, ctx->d, cache->pixmap, gc,
+ b.x, b.y, (unsigned)b.width, (unsigned)b.height, 0, 0);
+ }
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr,
+ ctx->optionTable, ctx->tkwin, cache->pixmap,
+ Ttk_MakeBox(0, 0, b.width, b.height), state);
+ XCopyArea(ctx->display, cache->pixmap, ctx->d, gc,
+ 0, 0, (unsigned)b.width, (unsigned)b.height, b.x, b.y);
+
+ cache->x = b.x; cache->y = b.y;
+ cache->w = b.width; cache->h = b.height;
+ cache->state = state;
+ cache->opaque = info.opaque;
+ cache->epoch = info.epoch;
+ cache->valid = 1;
+ DirtyAdd(ctx, b);
+ return;
+ }
+
+ /* Pixmap or GC allocation failed: fall through to a direct draw. */
+ if (cache) {
+ cache->valid = 0;
+ }
+ }
+
+ /* Direct draw: non-cacheable element, unrealized window, or alloc fail. */
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr, ctx->optionTable,
+ ctx->tkwin, ctx->d, b, state);
+ DirtyAdd(ctx, b);
+#else
+ (void)cacheSlot;
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr, ctx->optionTable,
+ ctx->tkwin, ctx->d, b, state);
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+}
+
+/*EOF*/
Index: generic/ttk/ttkTheme.c
==================================================================
--- A/generic/ttk/ttkTheme.c
+++ B/generic/ttk/ttkTheme.c
@@ -122,10 +122,12 @@
void *clientData; /* Client data passed in at registration time */
void *elementRecord; /* Scratch buffer for element record storage */
int nResources; /* #Element options */
Tcl_Obj **defaultValues; /* Array of option default values */
Tcl_HashTable optMapCache; /* Map: Tk_OptionTable * -> OptionMap */
+ unsigned cacheFlags; /* Render-cache policy (TTK_ELEMENT_* flags) */
+ Ttk_ElementCacheProc *cacheProc; /* Cache-info query, or NULL */
};
/* TTKGetOptionSpec --
* Look up a Tk_OptionSpec by name from a Tk_OptionTable,
* and verify that it's compatible with the specified Tk_OptionType,
@@ -250,12 +252,40 @@
/* Initialize option map cache:
*/
Tcl_InitHashTable(&elementClass->optMapCache, TCL_ONE_WORD_KEYS);
+ /* Render-cache policy: off until the element opts in.
+ */
+ elementClass->cacheFlags = 0;
+ elementClass->cacheProc = NULL;
+
return elementClass;
}
+
+/* TtkSetElementCachePolicy --
+ * Record an element class's render-cache policy: whether its composited
+ * output may be cached per layout node, and an optional query reporting
+ * its opacity and content epoch. Called once by an element after
+ * registration.
+ */
+void TtkSetElementCachePolicy(
+ Ttk_ElementClass *eclass,
+ unsigned cacheFlags,
+ Ttk_ElementCacheProc *cacheProc)
+{
+ eclass->cacheFlags = cacheFlags;
+ eclass->cacheProc = cacheProc;
+}
+
+/* Ttk_ElementClassCacheable --
+ * Return nonzero if the element class opted into per-node render caching.
+ */
+int Ttk_ElementClassCacheable(Ttk_ElementClass *eclass)
+{
+ return (eclass->cacheFlags & TTK_ELEMENT_CACHEABLE) != 0;
+}
/*
* FreeElementClass --
* Release resources associated with an element class record.
*/
@@ -1135,10 +1165,40 @@
}
eclass->specPtr->draw(
eclass->clientData, eclass->elementRecord,
tkwin, d, b, state);
}
+
+/* Ttk_ElementGetCacheInfo --
+ * Query a cacheable element's opacity and content epoch for the current
+ * (tkwin, state). Defaults to {not opaque, epoch 0} for elements that
+ * registered no cache proc, which is always a correct (if unoptimized)
+ * answer. Used by the per-node render cache in ttkLayout.c.
+ */
+void
+Ttk_ElementGetCacheInfo(
+ Ttk_ElementClass *eclass,
+ Ttk_Style style,
+ void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tk_Window tkwin,
+ Ttk_Box b,
+ Ttk_State state,
+ Ttk_ElementCacheInfo *info)
+{
+ info->opaque = 0;
+ info->epoch = 0;
+ if (eclass->cacheProc == NULL) {
+ return;
+ }
+ if (!InitializeElementRecord(
+ eclass, style, recordPtr, optionTable, tkwin, state)) {
+ return;
+ }
+ eclass->cacheProc(
+ eclass->clientData, eclass->elementRecord, tkwin, b, state, info);
+}
/*------------------------------------------------------------------------
* +++ 'style' command ensemble procedures.
*/
Index: generic/ttk/ttkThemeInt.h
==================================================================
--- A/generic/ttk/ttkThemeInt.h
+++ B/generic/ttk/ttkThemeInt.h
@@ -29,10 +29,58 @@
int *widthPtr, int *heightPtr, Ttk_Padding*);
MODULE_SCOPE void Ttk_DrawElement(
Ttk_ElementClass *, Ttk_Style, void *recordPtr, Tk_OptionTable,
Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state);
+/*
+ * Element render-cache policy (see the per-node cache in ttkLayout.c).
+ * An element class may opt into having its composited output cached per
+ * layout node, and may supply a query reporting whether it is fully opaque
+ * (i.e. background-independent) at a given state. Both default to off, so
+ * elements that do not opt in keep drawing directly with no pixmap.
+ */
+#define TTK_ELEMENT_CACHEABLE 0x1 /* opts into per-node render caching */
+
+/*
+ * Cache info an element reports for the current (tkwin, state): whether it is
+ * fully opaque (so its render is background-independent), and a content epoch
+ * that bumps whenever the element's own pixels change (e.g. an underlying photo
+ * is rewritten) so a stale node pixmap is rebuilt.
+ */
+typedef struct Ttk_ElementCacheInfo {
+ int opaque; /* element fully opaque (bg-independent) */
+ unsigned epoch; /* bumps when the element's own content changes */
+} Ttk_ElementCacheInfo;
+
+typedef void Ttk_ElementCacheProc(void *clientData, void *elementRecord,
+ Tk_Window tkwin, Ttk_Box b, Ttk_State state, Ttk_ElementCacheInfo *info);
+
+MODULE_SCOPE void TtkSetElementCachePolicy(
+ Ttk_ElementClass *, unsigned cacheFlags, Ttk_ElementCacheProc *);
+MODULE_SCOPE int Ttk_ElementClassCacheable(Ttk_ElementClass *);
+MODULE_SCOPE void Ttk_ElementGetCacheInfo(
+ Ttk_ElementClass *, Ttk_Style, void *recordPtr, Tk_OptionTable,
+ Tk_Window tkwin, Ttk_Box b, Ttk_State state, Ttk_ElementCacheInfo *);
+
+/*
+ * Per-node element render cache (ttkCache.c). The layout-draw traversal
+ * (ttkLayout.c) opens a draw context, draws each node's element through it, and
+ * closes it; the cache module owns the composited node pixmaps, the z-order
+ * invalidation, and all graphics state. Both types are opaque to the rest of
+ * Ttk; a layout node stores a NodeDrawCache* slot the cache module manages.
+ */
+typedef struct NodeDrawCache NodeDrawCache;
+typedef struct NodeDrawContext NodeDrawContext;
+
+MODULE_SCOPE NodeDrawContext *TtkNodeDrawBegin(
+ Ttk_Style style, void *recordPtr, Tk_OptionTable optionTable,
+ Tk_Window tkwin, Drawable d);
+MODULE_SCOPE void TtkDrawCachedElement(NodeDrawContext *,
+ Ttk_ElementClass *, NodeDrawCache **cacheSlot, Ttk_Box b, Ttk_State);
+MODULE_SCOPE void TtkNodeDrawEnd(NodeDrawContext *);
+MODULE_SCOPE void TtkFreeNodeDrawCache(NodeDrawCache *);
+
MODULE_SCOPE Tcl_Obj *Ttk_QueryStyle(
Ttk_Style, void *, Tk_OptionTable, const char *, Ttk_State state);
MODULE_SCOPE Ttk_LayoutTemplate Ttk_ParseLayoutTemplate(
Tcl_Interp *, Tcl_Obj *);
Index: unix/Makefile.in
==================================================================
--- A/unix/Makefile.in
+++ B/unix/Makefile.in
@@ -382,11 +382,12 @@
tkVisual.o tkWindow.o
TTK_OBJS = \
ttkBlink.o ttkButton.o ttkCache.o ttkClamTheme.o ttkClassicTheme.o \
ttkDefaultTheme.o ttkElements.o ttkEntry.o ttkFrame.o ttkImage.o \
- ttkInit.o ttkLabel.o ttkLayout.o ttkManager.o ttkNotebook.o \
+ ttkInit.o ttkLabel.o ttkLayout.o ttkManager.o ttkNodeCache.o \
+ ttkNotebook.o \
ttkPanedwindow.o ttkProgress.o ttkScale.o ttkScrollbar.o ttkScroll.o \
ttkSeparator.o ttkSquare.o ttkState.o \
ttkTagSet.o ttkTheme.o ttkTrace.o ttkTrack.o ttkTreeview.o \
ttkWidget.o ttkStubInit.o
@@ -484,10 +485,11 @@
$(TTK_DIR)/ttkImage.c \
$(TTK_DIR)/ttkInit.c \
$(TTK_DIR)/ttkLabel.c \
$(TTK_DIR)/ttkLayout.c \
$(TTK_DIR)/ttkManager.c \
+ $(TTK_DIR)/ttkNodeCache.c \
$(TTK_DIR)/ttkNotebook.c \
$(TTK_DIR)/ttkPanedwindow.c \
$(TTK_DIR)/ttkProgress.c \
$(TTK_DIR)/ttkScale.c \
$(TTK_DIR)/ttkScrollbar.c \
@@ -1512,10 +1514,13 @@
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkLabel.c
ttkLayout.o: $(TTK_DIR)/ttkLayout.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkLayout.c
+ttkNodeCache.o: $(TTK_DIR)/ttkNodeCache.c
+ $(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkNodeCache.c
+
ttkManager.o: $(TTK_DIR)/ttkManager.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkManager.c
ttkNotebook.o: $(TTK_DIR)/ttkNotebook.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkNotebook.c
Index: win/Makefile.in
==================================================================
--- A/win/Makefile.in
+++ B/win/Makefile.in
@@ -421,10 +421,11 @@
ttkImage.$(OBJEXT) \
ttkInit.$(OBJEXT) \
ttkLabel.$(OBJEXT) \
ttkLayout.$(OBJEXT) \
ttkManager.$(OBJEXT) \
+ ttkNodeCache.$(OBJEXT) \
ttkNotebook.$(OBJEXT) \
ttkPanedwindow.$(OBJEXT) \
ttkProgress.$(OBJEXT) \
ttkScale.$(OBJEXT) \
ttkScrollbar.$(OBJEXT) \
Index: win/configure
==================================================================
--- A/win/configure
+++ B/win/configure
@@ -4612,11 +4612,11 @@
if test "${GCC}" = "yes" ; then
SHLIB_LD=""
SHLIB_LD_LIBS='${LIBS}'
LIBS="-lnetapi32 -lkernel32 -luser32 -ladvapi32 -luserenv -lws2_32"
# mingw needs to link ole32 and oleaut32 for [send], but MSVC doesn't
- LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool"
+ LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool -lmsimg32"
STLIB_LD='${AR} cr'
RC_OUT=-o
RC_TYPE=
RC_INCLUDE=--include
RC_DEFINE=--define
@@ -4826,11 +4826,11 @@
CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}"
lflags="${lflags} -nologo"
LINKBIN="link"
fi
- LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib"
+ LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib msimg32.lib"
SHLIB_LD="${LINKBIN} -dll -incremental:no ${lflags}"
SHLIB_LD_LIBS='${LIBS}'
# link -lib only works when -lib is the first arg
STLIB_LD="${LINKBIN} -lib ${lflags}"
Index: win/makefile.vc
==================================================================
--- A/win/makefile.vc
+++ B/win/makefile.vc
@@ -295,10 +295,11 @@
$(TMP_DIR)\ttkImage.obj \
$(TMP_DIR)\ttkInit.obj \
$(TMP_DIR)\ttkLabel.obj \
$(TMP_DIR)\ttkLayout.obj \
$(TMP_DIR)\ttkManager.obj \
+ $(TMP_DIR)\ttkNodeCache.obj \
$(TMP_DIR)\ttkNotebook.obj \
$(TMP_DIR)\ttkPanedwindow.obj \
$(TMP_DIR)\ttkProgress.obj \
$(TMP_DIR)\ttkScale.obj \
$(TMP_DIR)\ttkScrollbar.obj \
@@ -345,11 +346,11 @@
!endif
PRJ_DEFINES = /DBUILD_ttk $(CONFIG_DEFS) /Dinline=__inline /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE
# Additional Link libraries needed beyond those in rules.vc
-PRJ_LIBS = netapi32.lib gdi32.lib user32.lib userenv.lib winspool.lib shell32.lib ole32.lib uuid.lib
+PRJ_LIBS = netapi32.lib gdi32.lib user32.lib userenv.lib winspool.lib shell32.lib ole32.lib uuid.lib msimg32.lib
#---------------------------------------------------------------------
# TkTest flags
#---------------------------------------------------------------------
Index: win/tcl.m4
==================================================================
--- A/win/tcl.m4
+++ B/win/tcl.m4
@@ -660,11 +660,11 @@
if test "${GCC}" = "yes" ; then
SHLIB_LD=""
SHLIB_LD_LIBS='${LIBS}'
LIBS="-lnetapi32 -lkernel32 -luser32 -ladvapi32 -luserenv -lws2_32"
# mingw needs to link ole32 and oleaut32 for [send], but MSVC doesn't
- LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool"
+ LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool -lmsimg32"
STLIB_LD='${AR} cr'
RC_OUT=-o
RC_TYPE=
RC_INCLUDE=--include
RC_DEFINE=--define
@@ -849,11 +849,11 @@
CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}"
lflags="${lflags} -nologo"
LINKBIN="link"
fi
- LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib"
+ LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib msimg32.lib"
SHLIB_LD="${LINKBIN} -dll -incremental:no ${lflags}"
SHLIB_LD_LIBS='${LIBS}'
# link -lib only works when -lib is the first arg
STLIB_LD="${LINKBIN} -lib ${lflags}"
Index: win/tkWinImage.c
==================================================================
--- A/win/tkWinImage.c
+++ B/win/tkWinImage.c
@@ -686,13 +686,254 @@
ReleaseDC(NULL, dc);
}
return imagePtr;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RGBA compositing support (TkpPutRGBAImage) --
+ *
+ * TkpPutRGBAImage composites an RGBA image onto a drawable with GDI
+ * AlphaBlend. It is called once per 9-slice tile, so creating and
+ * destroying a DIB section and memory DC on each call dominated the cost.
+ * A small per-thread source DIB + memory DC is therefore cached and
+ * reused, grown on demand up to TK_RGBA_DIB_CACHE_MAX per side; larger
+ * one-off draws bypass the cache and allocate transiently.
+ *
+ *----------------------------------------------------------------------
+ */
+
+#define TK_RGBA_DIB_CACHE_MAX 256 /* Cache DIBs up to this size per side. */
+
+typedef struct {
+ HDC memDC; /* Cached memory DC, or NULL. */
+ HBITMAP dib; /* DIB section selected into memDC, or NULL. */
+ HBITMAP oldBmp; /* memDC's original bitmap, restored on free. */
+ void *bits; /* Pixel pointer of dib. */
+ int w, h; /* Size dib was allocated at. */
+} RGBACache;
+
+static Tcl_ThreadDataKey rgbaCacheKey;
+
+/* RGBACacheExitHandler -- free this thread's cached DIB and memory DC. */
+static void
+RGBACacheExitHandler(
+ TCL_UNUSED(void *))
+{
+ RGBACache *cache = (RGBACache *)
+ Tcl_GetThreadData(&rgbaCacheKey, sizeof(RGBACache));
+
+ if (cache->memDC != NULL) {
+ if (cache->oldBmp != NULL) {
+ SelectObject(cache->memDC, cache->oldBmp);
+ }
+ DeleteDC(cache->memDC);
+ }
+ if (cache->dib != NULL) {
+ DeleteObject(cache->dib);
+ }
+ memset(cache, 0, sizeof(*cache));
+}
+
+/* CreateRGBADib -- a top-down 32-bit BGRA DIB section; returns it and bits. */
+static HBITMAP
+CreateRGBADib(
+ HDC refDC,
+ int w, int h,
+ void **bitsPtr)
+{
+ BITMAPINFO bmi;
+
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.bmiHeader.biWidth = w;
+ bmi.bmiHeader.biHeight = -h; /* negative = top-down */
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+ return CreateDIBSection(refDC, &bmi, DIB_RGB_COLORS, bitsPtr, NULL, 0);
+}
+
+/* GetRGBACache -- this thread's cached DIB + memory DC, grown to >= w x h. */
+static RGBACache *
+GetRGBACache(
+ HDC refDC,
+ int w, int h)
+{
+ RGBACache *cache = (RGBACache *)
+ Tcl_GetThreadData(&rgbaCacheKey, sizeof(RGBACache));
+
+ if (cache->memDC == NULL) {
+ cache->memDC = CreateCompatibleDC(refDC);
+ if (cache->memDC == NULL) {
+ return NULL;
+ }
+ Tcl_CreateThreadExitHandler(RGBACacheExitHandler, NULL);
+ }
+ if (cache->dib != NULL && (cache->w < w || cache->h < h)) {
+ SelectObject(cache->memDC, cache->oldBmp);
+ DeleteObject(cache->dib);
+ cache->dib = NULL;
+ }
+ if (cache->dib == NULL) {
+ int newW = (w > cache->w) ? w : cache->w;
+ int newH = (h > cache->h) ? h : cache->h;
+
+ cache->dib = CreateRGBADib(refDC, newW, newH, &cache->bits);
+ if (cache->dib == NULL) {
+ return NULL;
+ }
+ cache->oldBmp = (HBITMAP) SelectObject(cache->memDC, cache->dib);
+ cache->w = newW;
+ cache->h = newH;
+ }
+ return cache;
+}
+
+/*
+ * PremultiplyRGBA --
+ * Copy a w x h sub-rect of an RGBA image (alpha at byte 3) into a BGRA
+ * buffer of the given byte stride, premultiplying by alpha. AC_SRC_ALPHA
+ * wants premultiplied data; pix32 is RGBA and GDI DIBs are BGRA, so R and
+ * B are swapped.
+ */
+
+static void
+PremultiplyRGBA(
+ XImage *image,
+ int src_x, int src_y,
+ int w, int h,
+ unsigned char *dst,
+ int dstStride)
+{
+ int x, y;
+
+ for (y = 0; y < h; y++) {
+ unsigned char *s = (unsigned char *) image->data
+ + (size_t) (src_y + y) * image->bytes_per_line
+ + (size_t) src_x * 4;
+ unsigned char *o = dst + (size_t) y * dstStride;
+
+ for (x = 0; x < w; x++) {
+ unsigned int a = s[3];
+
+ o[0] = (unsigned char) ((s[2] * a + 127) / 255); /* B */
+ o[1] = (unsigned char) ((s[1] * a + 127) / 255); /* G */
+ o[2] = (unsigned char) ((s[0] * a + 127) / 255); /* R */
+ o[3] = (unsigned char) a; /* A */
+ s += 4;
+ o += 4;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkpPutRGBAImage --
+ *
+ * Composite an RGBA image (4 bytes/pixel, alpha at byte 3, NOT
+ * premultiplied) onto a drawable using GDI AlphaBlend (Porter-Duff
+ * source-over). Enabled by TK_CAN_RENDER_RGBA in tkWinPort.h; called by
+ * TkImgPhotoDisplay in place of the software BlendComplexAlpha path, which
+ * reads the destination back with XGetImage and blends per pixel on the
+ * CPU. Works for both window and pixmap drawables.
+ *
+ * Results:
+ * Success, or BadDrawable if a device context or DIB could not be
+ * obtained.
+ *
+ * Side effects:
+ * Draws onto the drawable.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkpPutRGBAImage(
+ Display *display,
+ Drawable drawable,
+ GC gc,
+ XImage *image, /* Source image; alpha at byte 3, not
+ * premultiplied. */
+ int src_x, int src_y, /* Top-left of the sub-rect within image. */
+ int dest_x, int dest_y, /* Top-left within drawable. */
+ unsigned int width, unsigned int height)
+{
+ TkWinDCState state;
+ HDC dstDC;
+ BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
+ int w = (int) width, h = (int) height;
+ (void) gc;
+
+ if (w <= 0 || h <= 0) {
+ return Success;
+ }
+
+ dstDC = TkWinGetDrawableDC(display, drawable, &state);
+ if (dstDC == NULL) {
+ return BadDrawable;
+ }
+
+ if (w <= TK_RGBA_DIB_CACHE_MAX && h <= TK_RGBA_DIB_CACHE_MAX) {
+ /*
+ * Common case: composite through the per-thread cached DIB + memory
+ * DC, so repeated small draws (e.g. 9-slice tiles) don't churn GDI
+ * objects. The DIB may be wider than w, so premultiply with its
+ * stride and AlphaBlend just the (0,0,w,h) sub-rect.
+ */
+
+ RGBACache *cache = GetRGBACache(dstDC, w, h);
+
+ if (cache != NULL) {
+ PremultiplyRGBA(image, src_x, src_y, w, h,
+ (unsigned char *) cache->bits, cache->w * 4);
+ if (!AlphaBlend(dstDC, dest_x, dest_y, w, h,
+ cache->memDC, 0, 0, w, h, bf)) {
+ BitBlt(dstDC, dest_x, dest_y, w, h, cache->memDC, 0, 0, SRCCOPY);
+ }
+ TkWinReleaseDrawableDC(drawable, dstDC, &state);
+ return Success;
+ }
+
+ /* Cache setup failed; fall through to a one-off allocation. */
+ }
+
+ /*
+ * Oversized draw (or cache allocation failed): one-off DIB + memory DC.
+ */
+
+ {
+ HDC memDC;
+ HBITMAP dib, oldBmp;
+ void *bits = NULL;
+
+ dib = CreateRGBADib(dstDC, w, h, &bits);
+ if (dib == NULL) {
+ TkWinReleaseDrawableDC(drawable, dstDC, &state);
+ return BadDrawable;
+ }
+ PremultiplyRGBA(image, src_x, src_y, w, h, (unsigned char *) bits, w * 4);
+
+ memDC = CreateCompatibleDC(dstDC);
+ oldBmp = (HBITMAP) SelectObject(memDC, dib);
+ if (!AlphaBlend(dstDC, dest_x, dest_y, w, h, memDC, 0, 0, w, h, bf)) {
+ BitBlt(dstDC, dest_x, dest_y, w, h, memDC, 0, 0, SRCCOPY);
+ }
+ SelectObject(memDC, oldBmp);
+ DeleteDC(memDC);
+ DeleteObject(dib);
+ }
+
+ TkWinReleaseDrawableDC(drawable, dstDC, &state);
+ return Success;
+}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/
Index: win/tkWinPort.h
==================================================================
--- A/win/tkWinPort.h
+++ B/win/tkWinPort.h
@@ -122,6 +122,19 @@
*/
#define TkpGetPixel(p) (((((p)->red >> 8) & 0xff) \
| ((p)->green & 0xff00) | (((p)->blue << 8) & 0xff0000)) | 0x20000000)
+/*
+ * Inform tkImgPhInstance.c that we implement TkpPutRGBAImage to composite RGBA
+ * images directly (via GDI AlphaBlend), so it uses that instead of the software
+ * BlendComplexAlpha path and its XGetImage read-back.
+ */
+
+#define TK_CAN_RENDER_RGBA
+
+MODULE_SCOPE int TkpPutRGBAImage(
+ Display *display, Drawable drawable, GC gc, XImage *image,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int width, unsigned int height);
+
#endif /* _WINPORT */