Subject: bug#16992: feature request: background images

From: Cecilio Pardo <[email-masked]>
Date: Fri, 8 Nov 2024 14:59:20 +0100

On 07/11/2024 7:04, Eli Zaretskii wrote:

> Yes, it's okay to post partial changesets for review.

Here is the incomplete patch that implements background images.

This should build and work for x+cairo and w32 (both mingw64 and
mingw.org's)

It works by adding a call to complex_bkg_clear_frame_area on all
functions that clear the background. These are the functions for w32
port. For the cairo port, they are parallel:

w32_clear_frame_area, w32_clear_glyph_string_rect,
w32_draw_stretch_glyph_string, w32font_draw

I know I'm missing at least w32_draw_image_glyph_string, but works well
enough for a test.

To setup the backgroud image for a frame, you would do something like
this:

(progn
(setq bkg (create-image "icons/hicolor/128x128/apps/emacs.png"))
(frame-set-background-image (selected-frame) bkg 'center nil)
(frame-set-background-policy (selected-frame) t))

There are new fields in the frame struct that control general activation
(background_policy), and the image to use (background_image).

The image can be placed in the center, or on sides or corners with 'n,
's, 'se, 'sw, etc. The background can be filled be repeating the image
passing t as the last parameter of frame-set-background-image.

Also, in this patch I am drawing a red line to mark the current
fill-column value for each window, to show how it would work.

The simple frame image code would not change much from where it is:
would add offsets, and more backends, and more points on the code that
may clear the background.

Right now, the background is cleared normally, and then the image is
painted over if the rect was cleared with the frame's background
pixel. So, frames with colored background will work. I haven't tested
yet with a default font that has a background different from the frame's
color.

Apart from the obvious overhead of drawing the image pixels,
unfortunately the frame contents can't be scrolled to reuse text pixels
(dispnew.c:scrolling_window). I don't think this can't be fixed without
big changes.

I attach a couple of screenshots to see what to expect.

From 13b92b39ae212081a51c67e8c3a639f13291a55c Mon Sep 17 00:00:00 2001
From: Cecilio Pardo <[email-masked]>
Date: Tue, 5 Nov 2024 23:41:30 +0100
Subject: [PATCH] Support for background images on frames.

* src/dispnew.c (scrolling_window):
* src/frame.c (make_frame):
(Fframe_set_background_image):
(Fframe_set_background_policy):
(syms_of_frame):
* src/frame.h (GCALIGNED_STRUCT):
* src/w32font.c (w32font_draw):
* src/w32term.c (w32_clear_glyph_string_rect):
(w32_draw_stretch_glyph_string):
(w32_need_to_draw_simple_background):
(w32_clear_frame_area_complex):
(w32_clear_frame_area):
* src/w32term.h:
* src/xterm.c (x_clear_frame_area):
---
 configure.ac     |   2 +-
 src/complexbkg.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++
 src/complexbkg.h |  31 ++++++
 src/dispnew.c    |   3 +
 src/frame.c      |  75 +++++++++++++
 src/frame.h      |  10 ++
 src/ftcrfont.c   |   8 ++
 src/w32font.c    |   8 ++
 src/w32term.c    |  20 +++-
 src/xterm.c      |  13 ++-
 src/xterm.h      |   3 +
 11 files changed, 439 insertions(+), 7 deletions(-)
 create mode 100644 src/complexbkg.c
 create mode 100644 src/complexbkg.h

diff --git a/configure.ac b/configure.ac
index 1c7545ef984..edb237f2051 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7630,7 +7630,7 @@ AC_DEFUN
   AC_DEFINE([HAVE_WINDOW_SYSTEM], [1], [Define if you have a window system.])
   AC_DEFINE([POLL_FOR_INPUT], [1],
     [Define if you poll periodically to detect C-g.])
-  WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o"
+  WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o complexbkg.o"
 
   if test "$window_system" = "x11" || test "$REALLY_ANDROID" = "yes"; then
     AC_DEFINE([HAVE_TEXT_CONVERSION], [1],
diff --git a/src/complexbkg.c b/src/complexbkg.c
new file mode 100644
index 00000000000..c3c5679df8b
--- /dev/null
+++ b/src/complexbkg.c
@@ -0,0 +1,273 @@
+#include "config.h"
+
+#if defined(HAVE_NTGUI)
+# include <windows.h>
+#endif
+
+#if defined(USE_CAIRO)
+# include "xterm.h"
+#endif
+
+#include "lisp.h"
+#include "frame.h"
+#include "buffer.h"
+#include "complexbkg.h"
+
+/*
+Each GUI backend must provide its own version of the functions
+get_bkg_color, begin_bkg_image, draw_bkg_image, end_bkg_image and
+draw_vertical_line. The functions are called from complexbkg_clear_frame_area
+
+- get_bkg_color: Although the background color is passed directly to
+  complexbkg_clear_frame_area, a backend may compute it from other
+  information.
+- begin_bkg_image: Called once, before any attempt to draw any image.
+- draw_bkg_image: Called any number of times, always between calls to
+  begin_bkg_image and end_bkg_image.
+- end_bkg_image: Called once, after all calls to draw_bkg_image.
+ */
+
+#if defined(HAVE_NTGUI)
+
+static void
+get_bkg_color (struct complexbkg_platform_info *data, unsigned long *bcolor)
+{
+  /* Nothing to see here  */
+}
+
+static void
+begin_bkg_image (struct complexbkg_platform_info *data, struct image *img)
+{
+  data->compat_hdc = CreateCompatibleDC (data->hdc);
+  data->orig_obj = SelectObject (data->compat_hdc, img->pixmap);
+}
+
+static void
+draw_bkg_image (struct complexbkg_platform_info *data, int dest_x, int dest_y,
+		int width, int height, int src_x, int src_y)
+{
+  BitBlt (data->hdc, dest_x, dest_y, width, height,
+	  data->compat_hdc, src_x, src_y, SRCCOPY);
+}
+
+static void
+end_bkg_image (struct complexbkg_platform_info *data)
+{
+  SelectObject (data->compat_hdc, data->orig_obj);
+  DeleteDC (data->compat_hdc);
+}
+
+static void
+draw_vertical_line (struct complexbkg_platform_info *data,
+		    int x, int y, int height)
+{
+  HANDLE pen_old = SelectObject (data->hdc,
+				 CreatePen (PS_SOLID, 1, 0x0000FF));
+  MoveToEx (data->hdc, x, y, NULL);
+  LineTo (data->hdc, x, y + height);
+  DeleteObject( SelectObject (data->hdc, pen_old) );
+}
+
+#elif defined(USE_CAIRO)
+
+/* The cairo backend uses the GC for the clipping and the background color.
+   If may be NULL.   */
+
+static void
+get_bkg_color (struct complexbkg_platform_info *data, unsigned long *bcolor)
+{
+  if (data->gc != NULL)
+    {
+      XGCValues xgcv;
+      XGetGCValues (FRAME_X_DISPLAY (data->frame),
+		    data->gc, GCBackground, &xgcv);
+      *bcolor = xgcv.background;
+    }
+}
+
+static void
+begin_bkg_image (struct complexbkg_platform_info *data, struct image *img)
+{
+  data->img = img;
+}
+
+static void
+draw_bkg_image (struct complexbkg_platform_info *data, int dest_x, int dest_y,
+		int width, int height, int src_x, int src_y)
+{
+  x_cr_draw_image( data->frame,
+		   data->gc == NULL ? FRAME_OUTPUT_DATA(data->frame)->normal_gc : data->gc,
+		   data->img->cr_data,
+		   src_x, src_y, width, height,
+		   dest_x, dest_y, false );
+}
+
+static void
+end_bkg_image (struct complexbkg_platform_info *data)
+{
+}
+
+static void
+draw_vertical_line (struct complexbkg_platform_info *data,
+		    int x, int y, int height)
+{
+  cairo_t *cr;
+  cr = x_begin_cr_clip (data->frame, NULL);
+  cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
+  cairo_move_to (cr, x, y);
+  cairo_line_to (cr, x, y + height);
+  cairo_stroke (cr);
+  x_end_cr_clip(data->frame);
+}
+
+#else /* GUI type not supported.  */
+#warning "complexbkg disabled: GUI type not supported."
+#define DISABLE_COMPLEXBKG 1
+#endif
+
+#ifndef DISABLE_COMPLEXBKG
+static void
+get_all_live_windows (struct window *w, struct window **dest)
+{
+  if (WINDOW_LEAF_P (w))
+    {
+      int i = 0;
+      for (; dest[i]; i++)
+	;
+      dest[i] = w;
+      dest[i+1] = NULL;
+    }
+
+  if (WINDOW_INTERNAL_P (w))
+      for (Lisp_Object ch = w->contents;
+	   ch != Qnil;
+	   ch = XWINDOW (ch)->next )
+        get_all_live_windows (XWINDOW (ch), dest);
+}
+
+void
+complexbkg_clear_frame_area (struct frame *f, unsigned long bcolor,
+			     int x, int y, int width, int height,
+			     struct complexbkg_platform_info *data)
+{
+  if (f->background_policy == Qnil)
+    return;
+
+  get_bkg_color (data, &bcolor);
+
+  if (f->background_image >= 0 && bcolor == FRAME_BACKGROUND_PIXEL (f))
+    {
+      struct image *img = IMAGE_FROM_ID (f, f->background_image);
+      prepare_image_for_display (f, img);
+
+      if (!img->load_failed_p)
+	{
+	  begin_bkg_image (data, img);
+
+	  if (f->background_tiled)
+	    {
+	      int x1 = x + width, y1 = y + height;
+	      /* Range of tiles that need painting.  */
+	      int tx0 = x / img->width;
+	      int tx1 = (x + width - 1) / img->width;
+	      int ty0 = y / img->height;
+	      int ty1 = (y + height - 1) / img->height;
+
+	      for (int tx = tx0; tx <= tx1; tx++)
+		for (int ty = ty0; ty <= ty1; ty++)
+		  {
+		    int X = tx * img->width, Y = ty * img->width;
+		    int X1 = X + img->width, Y1 = Y + img->height;
+
+		    /* Clip the tile against the output rectangle.  */
+		    int
+		      c_left   = (X < x)   ? (x - X)   : 0,
+		      c_right  = (X1 > x1) ? (X1 - x1) : 0,
+		      c_top    = (Y < y)   ? (y - Y)   : 0,
+		      c_bottom = (Y1 > y1) ? (Y1 - y1) : 0;
+
+		    draw_bkg_image (data,
+				    X + c_left, Y + c_top,
+				    img->width - c_right - c_left,
+				    img->height - c_bottom - c_top,
+				    c_left, c_top );
+		  }
+	    }
+	  else
+	    {
+	      int src_x = x, src_y = y;
+	      int fw = FRAME_PIXEL_WIDTH (f), fh = FRAME_PIXEL_HEIGHT (f);
+
+	      switch (f->background_gravity)
+		{
+		case 1: /* north */
+		  src_x -= (fw - img->width) / 2;
+		  break;
+		case 2: /* south */
+		  src_x -= (fw - img->width) / 2;
+		  src_y -= fh - img->height;
+		  break;
+		case 3: /* east */
+		  src_x -= fw - img->width;
+		  src_y -= (fh - img->height) / 2;
+		  break;
+		case 4: /* west */
+		  src_y -= (fh - img->height) / 2;
+		  break;
+		case 5: /* ne */
+		  src_x -= fw - img->width;
+		  break;
+		case 6: /* nw */
+		  break;
+		case 7: /* se */
+		  src_x -= fw - img->width;
+		  src_y -= fh - img->height;
+		  break;
+		case 8: /* sw */
+		  src_y -= fh - img->height;
+		  break;
+		case 9: /* center */
+		  src_x -= (fw - img->width) / 2;
+		  src_y -= (fh - img->height) / 2;
+		  break;
+		}
+
+	      draw_bkg_image (data, x, y, width, height, src_x, src_y);
+	    }
+	  end_bkg_image (data);
+	}
+    }
+
+  /* Window specific complications.  */
+  struct window *windows[f->number_of_windows + 1];
+  windows[0] = NULL;
+  get_all_live_windows (XWINDOW (f->root_window), windows);
+
+  Emacs_Rectangle zone = { x, y, width, height };
+  for (int i = 0; windows[i]; i++)
+    {
+      int boxx, boxy, boxwidth, boxheight;
+      window_box(windows[i], TEXT_AREA, &boxx, &boxy, &boxwidth, &boxheight);
+      Emacs_Rectangle wbox, intersect;
+      wbox.x = boxx;
+      wbox.y = boxy;
+      wbox.width = boxwidth;
+      wbox.height = boxheight;
+      if (!gui_intersect_rectangles( &zone, &wbox, &intersect ))
+	continue;
+      /* This window has to update the 'intersect' rectangle.  */
+
+      Lisp_Object fill_column = BVAR (XBUFFER (WINDOW_BUFFER (windows[i])), fill_column);
+      if (FIXNUMP (fill_column))
+	{
+	  /* TODO: Take the buffer's face with  */
+	  int vertical_line_x = XFIXNUM (fill_column) * FRAME_COLUMN_WIDTH(f);
+
+	  if (wbox.x + vertical_line_x >= intersect.x
+	      && wbox.x + vertical_line_x <= intersect.x + intersect.width)
+	    draw_vertical_line (data, wbox.x + vertical_line_x,
+				intersect.y, intersect.height);
+	}
+    }
+}
+#endif /* DISABLE_COMPLEXBKG */
diff --git a/src/complexbkg.h b/src/complexbkg.h
new file mode 100644
index 00000000000..e45ebb2edff
--- /dev/null
+++ b/src/complexbkg.h
@@ -0,0 +1,31 @@
+#if defined(HAVE_NTGUI)
+
+struct complexbkg_platform_info {
+  HDC hdc;
+  /* Internal from here  */
+  HDC compat_hdc;
+  HGDIOBJ orig_obj;
+};
+
+#elif defined(USE_CAIRO)
+
+struct complexbkg_platform_info {
+  struct frame *frame;
+  GC gc;
+  /* Internal from here  */
+  struct image *img;
+};
+
+#else
+
+struct complexbkg_platform_info {
+  int dummy;
+};
+
+#endif
+
+void complexbkg_clear_frame_area (struct frame *f,
+				  unsigned long bcolor,
+				  int x, int y,
+				  int width, int height,
+				  struct complexbkg_platform_info *data);
diff --git a/src/dispnew.c b/src/dispnew.c
index 1a243079e46..ca564cd0c46 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -4549,6 +4549,9 @@ scrolling_window (struct window *w, int tab_line_p)
   struct row_entry *entry;
   struct redisplay_interface *rif = FRAME_RIF (XFRAME (WINDOW_FRAME (w)));
 
+  if (XFRAME (WINDOW_FRAME (w))->background_policy != Qnil)
+    return 0;
+
   /* Skip over rows equal at the start.  */
   for (i = tab_line_p; i < current_matrix->nrows - 1; ++i)
     {
diff --git a/src/frame.c b/src/frame.c
index 7f4bf274ad9..d2ff749f49d 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -1009,6 +1009,11 @@ make_frame (bool mini_p)
   f->conversion.actions = NULL;
 #endif
 
+#ifdef HAVE_WINDOW_SYSTEM
+  f->background_policy = Qnil;
+  f->background_image = -1;
+#endif /* HAVE_WINDOW_SYSTEM */
+
   root_window = make_window ();
   rw = XWINDOW (root_window);
   if (mini_p)
@@ -1696,6 +1701,64 @@ DEFUN ("frame-list", Fframe_list, Sframe_list,
 #endif /* HAVE_WINDOW_SYSTEM */
 }
 
+DEFUN("frame-set-background-image", Fframe_set_background_image,
+      Sframe_set_background_image, 2, 4, 0,
+      doc: /* Set the background image of FRAME to IMAGE.  If present,
+GRAVITY must one of symbols: n, s, e, w, ne, nw, se, sw, center. If
+TILED it t, repeat the image to fill the whole frame, excetp if GRAVITY
+is center.  */ )
+  (Lisp_Object frame, Lisp_Object image_spec, Lisp_Object gravity, Lisp_Object tiled)
+{
+  struct frame *f = decode_live_frame (frame);
+  if (NILP (image_spec))
+      f->background_image = -1;
+  else
+    {
+      ptrdiff_t image_id = lookup_image (f, image_spec, DEFAULT_FACE_ID);
+      f->background_image = image_id;
+    }
+
+  f->background_gravity = 6; /* north west  */
+  if (gravity == Qn)
+    f->background_gravity = 1;
+  else if (gravity == Qs)
+    f->background_gravity = 2;
+  else if (gravity == Qe)
+    f->background_gravity = 3;
+  else if (gravity == Qw)
+    f->background_gravity = 4;
+  else if (gravity == Qne)
+    f->background_gravity = 5;
+  else if (gravity == Qnw)
+    f->background_gravity = 6;
+  else if (gravity == Qse)
+    f->background_gravity = 7;
+  else if (gravity == Qsw)
+    f->background_gravity = 8;
+  else if (gravity == Qcenter)
+    f->background_gravity = 9;
+
+  f->background_tiled = true;
+  if (tiled == Qnil)
+    f->background_tiled = false;
+
+  SET_FRAME_GARBAGED (f);
+  return image_spec;
+}
+
+DEFUN("frame-set-background-policy", Fframe_set_background_policy,
+      Sframe_set_background_policy, 2, 2, 0,
+      doc: /* Set the background policy for FRAME.  */ )
+  (Lisp_Object frame, Lisp_Object bp)
+{
+  struct frame *f = decode_live_frame (frame);
+
+  f->background_policy = bp;
+  SET_FRAME_GARBAGED (f);
+
+  return bp;
+}
+
 DEFUN ("frame-parent", Fframe_parent, Sframe_parent,
        0, 1, 0,
        doc: /* Return the parent frame of FRAME.
@@ -6817,6 +6880,8 @@ focus (where a frame immediately loses focus when it's left by the mouse
   frame_internal_parameters = list3 (Qname, Qparent_id, Qwindow_id);
 #endif
 
+  defsubr (&Sframe_set_background_image);
+  defsubr (&Sframe_set_background_policy);
   defsubr (&Sframep);
   defsubr (&Sframe_live_p);
   defsubr (&Swindow_system);
@@ -6901,5 +6966,15 @@ focus (where a frame immediately loses focus when it's left by the mouse
 #if !defined HAVE_EXT_TOOL_BAR || defined USE_GTK
   Fprovide (Qmove_toolbar, Qnil);
 #endif /* !HAVE_EXT_TOOL_BAR || USE_GTK */
+
+  DEFSYM (Qn, "n");
+  DEFSYM (Qs, "s");
+  DEFSYM (Qe, "e");
+  DEFSYM (Qw, "w");
+  DEFSYM (Qne, "ne");
+  DEFSYM (Qnw, "nw");
+  DEFSYM (Qse, "se");
+  DEFSYM (Qsw, "sw");
+  DEFSYM (Qcenter, "center");
 #endif /* HAVE_WINDOW_SYSTEM */
 }
diff --git a/src/frame.h b/src/frame.h
index 1d920d1a6bc..7c2d82c1be3 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -258,6 +258,9 @@ #define EMACS_FRAME_H
   /* Desired and current contents displayed in that window.  */
   Lisp_Object desired_tab_bar_string;
   Lisp_Object current_tab_bar_string;
+
+  /* Configuration for background image.  */
+  Lisp_Object background_policy;
 #endif
 
 #if defined (HAVE_WINDOW_SYSTEM) && ! defined (HAVE_EXT_TOOL_BAR)
@@ -741,6 +744,13 @@ #define EMACS_FRAME_H
   /* Text conversion state used by certain input methods.  */
   struct text_conversion_state conversion;
 #endif
+
+#ifdef HAVE_WINDOW_SYSTEM
+  ptrdiff_t background_image;
+  int background_gravity;
+  bool background_tiled;
+#endif /* HAVE_WINDOW_SYSTEM */
+
 } GCALIGNED_STRUCT;
 
 /* Most code should use these functions to set Lisp fields in struct frame.  */
diff --git a/src/ftcrfont.c b/src/ftcrfont.c
index 3700154e44a..dd019275f43 100644
--- a/src/ftcrfont.c
+++ b/src/ftcrfont.c
@@ -40,6 +40,8 @@
 #ifdef HAVE_PGTK
 #include "xsettings.h"
 #endif
+#include "complexbkg.h"
+
 
 #ifdef USE_BE_CAIRO
 #define RED_FROM_ULONG(color)	(((color) >> 16) & 0xff)
@@ -601,6 +603,12 @@ ftcrfont_draw (struct glyph_string *s,
       cairo_rectangle (cr, x, y - FONT_BASE (s->font),
 		       s->width, FONT_HEIGHT (s->font));
       cairo_fill (cr);
+
+      struct complexbkg_platform_info data = { s->f, s->gc };;
+      complexbkg_clear_frame_area (s->f, 0,
+				   x, y - FONT_BASE (s->font),
+				   s->width, FONT_HEIGHT (s->font),
+				   &data );
     }
 
   glyphs = alloca (sizeof (cairo_glyph_t) * len);
diff --git a/src/w32font.c b/src/w32font.c
index 48968a28fbd..30676e23ccc 100644
--- a/src/w32font.c
+++ b/src/w32font.c
@@ -24,6 +24,7 @@
 #include <commdlg.h>
 
 #include "lisp.h"
+#include "complexbkg.h"
 #include "w32term.h"
 #include "frame.h"
 #include "coding.h"	/* for ENCODE_SYSTEM, DECODE_SYSTEM */
@@ -696,6 +697,7 @@ w32font_draw (struct glyph_string *s, int from, int to,
 	    - s->first_glyph->slice.glyphless.upper_yoff;
 	  descent = 0;
 	}
+
       brush = CreateSolidBrush (s->gc->background);
       rect.left = x;
       rect.top = y - ascent;
@@ -703,6 +705,12 @@ w32font_draw (struct glyph_string *s, int from, int to,
       rect.bottom = y + descent;
       FillRect (s->hdc, &rect, brush);
       DeleteObject (brush);
+
+      struct complexbkg_platform_info data = { s->hdc };
+
+      complexbkg_clear_frame_area (s->f, s->gc->background,
+				   x, y - ascent, s->width,
+				   ascent + descent, &data);
     }
 
   if (s->padding_p)
diff --git a/src/w32term.c b/src/w32term.c
index e18f39dd2a8..6663fa1d5e8 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -22,6 +22,7 @@ Copyright (C) 1989, 1993-2024 Free Software Foundation, Inc.
 #include <stdio.h>
 #include "lisp.h"
 #include "blockinput.h"
+#include "complexbkg.h"
 #include "w32term.h"
 #include "w32common.h"	/* for OS version info */
 #include <wtypes.h>
@@ -1285,8 +1286,11 @@ w32_clear_glyph_string_rect (struct glyph_string *s,
                     - s->gc->clip_rectangle.top);
     }
 #endif
-  w32_fill_area (s->f, s->hdc, s->gc->background, real_x, real_y,
-                 real_w, real_h);
+    w32_fill_area (s->f, s->hdc, s->gc->background, real_x, real_y,
+		   real_w, real_h);
+    struct complexbkg_platform_info data = { s->hdc };
+    complexbkg_clear_frame_area (s->f, s->gc->background,
+				 real_x, real_y, real_w, real_h, &data);
 }
 
 /* Fill background with bitmap pattern from S at specified position
@@ -2604,7 +2608,10 @@ w32_draw_stretch_glyph_string (struct glyph_string *s)
 	    }
 	  else
             {
-              w32_fill_area (s->f, s->hdc, gc->background, x, y, w, h);
+	      w32_fill_area (s->f, s->hdc, gc->background, x, y, w, h);
+	      struct complexbkg_platform_info data = { s->hdc };
+	      complexbkg_clear_frame_area (s->f, gc->background,
+					   x, y, w, h, &data);
             }
         }
     }
@@ -6626,7 +6633,6 @@ w32_draw_bar_cursor (struct window *w, struct glyph_row *row,
     }
 }
 
-
 /* RIF: Define cursor CURSOR on frame F.  */
 
 static void
@@ -6635,7 +6641,6 @@ w32_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
   w32_define_cursor (FRAME_W32_WINDOW (f), cursor);
 }
 
-
 /* RIF: Clear area on frame F.  */
 
 static void
@@ -6644,7 +6649,12 @@ w32_clear_frame_area (struct frame *f, int x, int y, int width, int height)
   HDC hdc;
 
   hdc = get_frame_dc (f);
+
   w32_clear_area (f, hdc, x, y, width, height);
+  struct complexbkg_platform_info data = { hdc };
+  complexbkg_clear_frame_area (f, FRAME_BACKGROUND_PIXEL(f),
+			       x, y, width, height, &data);
+
   release_frame_dc (f, hdc);
 }
 
diff --git a/src/xterm.c b/src/xterm.c
index 0c20d38b0f7..653dcfb2e8c 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -719,6 +719,8 @@ Copyright (C) 1989, 1993-2024 Free Software Foundation, Inc.
 #include "sysselect.h"
 #include "menu.h"
 #include "pdumper.h"
+#include "complexbkg.h"
+
 
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
@@ -6288,7 +6290,7 @@ x_cr_destroy_fringe_bitmap (int which)
   fringe_bmp[which] = 0;
 }
 
-static void
+void
 x_cr_draw_image (struct frame *f, GC gc, cairo_pattern_t *image,
 		 int src_x, int src_y, int width, int height,
 		 int dest_x, int dest_y, bool overlay_p)
@@ -8530,6 +8532,8 @@ x_compute_glyph_string_overhangs (struct glyph_string *s)
 x_clear_glyph_string_rect (struct glyph_string *s, int x, int y, int w, int h)
 {
   x_clear_rectangle (s->f, s->gc, x, y, w, h, s->hl != DRAW_CURSOR);
+  struct complexbkg_platform_info data = { s->f, s->gc };
+  complexbkg_clear_frame_area (s->f, 0, x, y, w, h, &data);
 }
 
 #ifndef USE_CAIRO
@@ -10713,6 +10717,10 @@ x_draw_stretch_glyph_string (struct glyph_string *s)
 	      XSetForeground (display, gc, xgcv.background);
 	      x_fill_rectangle (s->f, gc, x, y, w, h, true);
 	      XSetForeground (display, gc, xgcv.foreground);
+
+	      struct complexbkg_platform_info data = { s->f, gc };
+	      complexbkg_clear_frame_area (s->f, 0,
+					   x, y, w, h, &data);
 	    }
 
 	  x_reset_clip_rectangles (s->f, gc);
@@ -26039,6 +26047,9 @@ x_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
 x_clear_frame_area (struct frame *f, int x, int y, int width, int height)
 {
   x_clear_area (f, x, y, width, height);
+  struct complexbkg_platform_info data = { f, NULL };
+  complexbkg_clear_frame_area (f, FRAME_BACKGROUND_PIXEL(f),
+			       x, y, width, height, &data);
 }

diff --git a/src/xterm.h b/src/xterm.h
index 8d5c9917749..ca7c745b369 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -1816,6 +1816,9 @@ #define SELECTION_EVENT_TIME(eventp)	\
 extern void x_set_cr_source_with_gc_background (struct frame *, GC, bool);
 extern void x_cr_draw_frame (cairo_t *, struct frame *);
 extern Lisp_Object x_cr_export_frames (Lisp_Object, cairo_surface_type_t);
+extern void x_cr_draw_image (struct frame *f, GC gc, cairo_pattern_t *image,
+			     int src_x, int src_y, int width, int height,
+			     int dest_x, int dest_y, bool overlay_p);
 #endif
 
 #ifdef HAVE_XFIXES
-- 
2.35.1.windows.2