Home | History | Annotate | Line # | Download | only in dist
      1 /* $XTermId: graphics.c,v 1.137 2024/12/01 20:21:19 tom Exp $ */
      2 
      3 /*
      4  * Copyright 2013-2023,2024 by Thomas E. Dickey
      5  * Copyright 2013-2022,2023 by Ross Combs
      6  *
      7  *                         All Rights Reserved
      8  *
      9  * Permission is hereby granted, free of charge, to any person obtaining a
     10  * copy of this software and associated documentation files (the
     11  * "Software"), to deal in the Software without restriction, including
     12  * without limitation the rights to use, copy, modify, merge, publish,
     13  * distribute, sublicense, and/or sell copies of the Software, and to
     14  * permit persons to whom the Software is furnished to do so, subject to
     15  * the following conditions:
     16  *
     17  * The above copyright notice and this permission notice shall be included
     18  * in all copies or substantial portions of the Software.
     19  *
     20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     23  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
     24  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     25  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     26  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     27  *
     28  * Except as contained in this notice, the name(s) of the above copyright
     29  * holders shall not be used in advertising or otherwise to promote the
     30  * sale, use or other dealings in this Software without prior written
     31  * authorization.
     32  */
     33 
     34 #include <xterm.h>
     35 
     36 #include <stdio.h>
     37 #include <ctype.h>
     38 #include <stdlib.h>
     39 
     40 #include <data.h>
     41 #include <ptyx.h>
     42 
     43 #include <assert.h>
     44 #include <graphics.h>
     45 
     46 #define OPT_INHERIT_COLORS 0
     47 
     48 #if OPT_REGIS_GRAPHICS
     49 #include <graphics_regis.h>
     50 #endif
     51 
     52 #undef DUMP_BITMAP
     53 #undef DUMP_COLORS
     54 #undef DEBUG_PALETTE
     55 #undef DEBUG_PIXEL
     56 #undef DEBUG_REFRESH
     57 
     58 /*
     59  * graphics TODO list
     60  *
     61  * ReGIS:
     62  * - ship a default alphabet zero font instead of scaling Xft fonts
     63  * - input cursors
     64  * - output cursors
     65  * - mouse/tablet/arrow-key input
     66  * - fix graphic pages for ReGIS -- they should also apply to text and sixel graphics
     67  * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
     68  * - non-ASCII alphabets
     69  * - enter/leave anywhere in a command
     70  * - locator key definitions (DECLKD)
     71  * - command display mode
     72  * - re-rasterization on window resize
     73  * - macros
     74  * - improved fills for narrow angles (track actual lines not just pixels)
     75  * - hardcopy/screen-capture support (need dialog of some sort for safety)
     76  * - error reporting
     77  *
     78  * sixel:
     79  * - fix problem where new_row < 0 during sixel parsing (see FIXME)
     80  * - screen-capture support (need dialog of some sort for safety)
     81  *
     82  * VT55/VT105 waveform graphics
     83  * - everything
     84  *
     85  * Tektronix:
     86  * - color (VT340 4014 emulation, 41xx, IRAF GTERM, and also MS-DOS Kermit color support)
     87  * - polygon fill (41xx)
     88  * - clear area extension
     89  * - area fill extension
     90  * - pixel operations (RU/RS/RP)
     91  * - research other 41xx and 42xx extensions
     92  *
     93  * common graphics features:
     94  * - handle light/dark screen modes (CSI?5[hl])
     95  * - update text fg/bg color which overlaps images
     96  * - handle graphic updates in scroll regions (verify effect on graphics)
     97  * - handle rectangular area copies (verify they work with graphics)
     98  * - invalidate graphics under graphic if same origin, at least as big, and bg not transparent
     99  * - invalidate graphic if completely scrolled past end of scrollback
    100  * - invalidate graphic if all pixels are transparent/erased
    101  * - invalidate graphic if completely scrolled out of alt buffer
    102  * - posturize requested colors to match hardware palettes (e.g. only four possible shades on VT240)
    103  * - color register report/restore
    104  * - ability to select/copy graphics for pasting in other programs
    105  * - ability to show non-scroll-mode sixel graphics in a separate window
    106  * - ability to show ReGIS graphics in a separate window
    107  * - ability to show Tektronix graphics in VT100 window
    108  * - truncate graphics at bottom edge of terminal?
    109  * - locator events (DECEFR DECSLE DECELR DECLRP)
    110  * - locator controller mode (CSI6i / CSI7i)
    111  *
    112  * new escape sequences:
    113  * - way to query text font size without "window ops" (or make "window ops" permissions more fine grained)
    114  * - way to query and set the number of graphics pages
    115  *
    116  * ReGIS extensions:
    117  * - non-integer text scaling
    118  * - free distortionless text rotation (vs. simulating the distortion and aligning to 45deg increments)
    119  * - font characteristics: bold/underline/italic
    120  * - remove/increase arbitrary limits (pattern size, pages, alphabets, stack size, font names, etc.)
    121  * - shade/fill with borders
    122  * - sprites (copy portion of page into/out of buffer with scaling and rotation)
    123  * - ellipses
    124  * - 2D patterns
    125  * - option to set actual graphic size (not just coordinate range)
    126  * - gradients (for lines and fills)
    127  * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
    128  * - transparency
    129  * - background color as stackable write control
    130  * - true color (virtual color registers created upon lookup)
    131  * - anti-aliasing
    132  * - variable-width (proportional) text
    133  */
    134 
    135 /* font sizes:
    136  * VT510:
    137  *   80 Columns 132 Columns Maximum Number of Lines
    138  *   10 x 16   6 x 16  26 lines + keyboard indicator line
    139  *   10 x 13   6 x 13  26 lines + keyboard indicator line
    140  *   10 x 10   6 x 10  42 lines + keyboard indicator line
    141  *   10 x 8    6 x 8   53 lines + keyboard indicator line
    142  */
    143 
    144 typedef struct allocated_color_register {
    145     struct allocated_color_register *next;
    146     Pixel pix;
    147     short r, g, b;
    148 } AllocatedColorRegister;
    149 
    150 #define LOOKUP_WIDTH 16
    151 static AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
    152 
    153 #define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
    154 
    155 static ColorRegister *shared_color_registers;
    156 static Graphic *displayed_graphics[MAX_GRAPHICS];
    157 static unsigned next_graphic_id = 0U;
    158 static unsigned used_graphics;	/* 0 to MAX_GRAPHICS */
    159 
    160 static int valid_graphics;
    161 static GC graphics_gc;
    162 static XGCValues xgcv;
    163 static ColorRegister last_color;
    164 static ColorRegister gc_color;
    165 
    166 #define DiffColor(this,that) \
    167 	(this.r != that.r || \
    168 	 this.g != that.g || \
    169 	 this.b != that.b)
    170 
    171 static ColorRegister null_color =
    172 {-1, -1, -1};
    173 
    174 static ColorRegister *
    175 allocRegisters(void)
    176 {
    177     return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
    178 }
    179 
    180 static Graphic *
    181 freeGraphic(Graphic *obj)
    182 {
    183     if (obj) {
    184 	free(obj->pixels);
    185 	free(obj->private_color_registers);
    186 	free(obj);
    187     }
    188     return NULL;
    189 }
    190 
    191 static Graphic *
    192 allocGraphic(int max_w, int max_h)
    193 {
    194     Graphic *result = TypeCalloc(Graphic);
    195     if (result) {
    196 	result->max_width = max_w;
    197 	result->max_height = max_h;
    198 	if (!(result->pixels = TypeCallocN(RegisterNum,
    199 					     (size_t) max_w * (size_t) max_h))) {
    200 	    result = freeGraphic(result);
    201 	} else if (!(result->private_color_registers = allocRegisters())) {
    202 	    result = freeGraphic(result);
    203 	}
    204     }
    205     return result;
    206 }
    207 
    208 #define getActiveSlot(n) \
    209 	(((n) < MAX_GRAPHICS && \
    210 	 displayed_graphics[n] && \
    211 	 displayed_graphics[n]->valid) \
    212 	 ? displayed_graphics[n] \
    213 	 : NULL)
    214 
    215 static Graphic *
    216 getInactiveSlot(const TScreen *screen, unsigned n)
    217 {
    218     if (n < MAX_GRAPHICS &&
    219 	(!displayed_graphics[n] ||
    220 	 !displayed_graphics[n]->valid)) {
    221 	if (!displayed_graphics[n]) {
    222 	    displayed_graphics[n] = allocGraphic(screen->graphics_max_wide,
    223 						 screen->graphics_max_high);
    224 	    used_graphics += (displayed_graphics[n] != NULL);
    225 	}
    226 	return displayed_graphics[n];
    227     }
    228     return NULL;
    229 }
    230 
    231 static ColorRegister *
    232 getSharedRegisters(void)
    233 {
    234     if (!shared_color_registers)
    235 	shared_color_registers = allocRegisters();
    236     return shared_color_registers;
    237 }
    238 
    239 static void
    240 deactivateSlot(unsigned n)
    241 {
    242     if ((n < MAX_GRAPHICS) && displayed_graphics[n]) {
    243 	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
    244 	used_graphics--;
    245     }
    246 }
    247 
    248 extern RegisterNum
    249 read_pixel(Graphic *graphic, int x, int y)
    250 {
    251     return (((x) >= 0 &&
    252 	     (x) < (graphic)->actual_width &&
    253 	     (y) >= 0 &&
    254 	     (y) < (graphic)->actual_height)
    255 	    ? (graphic)->pixels[(y) * (graphic)->max_width + (x)]
    256 	    : (RegisterNum) COLOR_HOLE);
    257 }
    258 
    259 #define _draw_pixel(G, X, Y, C) \
    260     do { \
    261         unsigned _cell = (unsigned)((Y) * (G)->max_width + (X)); \
    262         SetSpixel(G, _cell, (RegisterNum) (C)); \
    263     } while (0)
    264 
    265 void
    266 draw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
    267 {
    268     assert(color <= MAX_COLOR_REGISTERS);
    269 
    270 #ifdef DEBUG_PIXEL
    271     TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
    272 	   x,
    273 	   y,
    274 	   color,
    275 	   COLOR_HOLE,
    276 	   ((color != COLOR_HOLE)
    277 	    ? (unsigned) graphic->color_registers[color].r : 0U),
    278 	   ((color != COLOR_HOLE)
    279 	    ? (unsigned) graphic->color_registers[color].g : 0U),
    280 	   ((color != COLOR_HOLE)
    281 	    ? (unsigned) graphic->color_registers[color].b : 0U)));
    282 #endif
    283     if (x >= 0 && x < graphic->actual_width &&
    284 	y >= 0 && y < graphic->actual_height) {
    285 	_draw_pixel(graphic, x, y, color);
    286 	if (color < MAX_COLOR_REGISTERS)
    287 	    graphic->color_registers_used[color] = True;
    288     }
    289 }
    290 
    291 void
    292 draw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
    293 {
    294     int x, y;
    295     int tmp;
    296 
    297     assert(color <= MAX_COLOR_REGISTERS);
    298 
    299     if (x1 > x2) {
    300 	EXCHANGE(x1, x2, tmp);
    301     }
    302     if (y1 > y2) {
    303 	EXCHANGE(y1, y2, tmp);
    304     }
    305 
    306     if (x2 < 0 || x1 >= graphic->actual_width ||
    307 	y2 < 0 || y1 >= graphic->actual_height)
    308 	return;
    309 
    310     if (x1 < 0)
    311 	x1 = 0;
    312     if (x2 >= graphic->actual_width)
    313 	x2 = graphic->actual_width - 1;
    314     if (y1 < 0)
    315 	y1 = 0;
    316     if (y2 >= graphic->actual_height)
    317 	y2 = graphic->actual_height - 1;
    318 
    319     if (color < MAX_COLOR_REGISTERS)
    320 	graphic->color_registers_used[color] = True;
    321     for (y = y1; y <= y2; y++)
    322 	for (x = x1; x <= x2; x++)
    323 	    _draw_pixel(graphic, x, y, color);
    324 }
    325 
    326 void
    327 copy_overlapping_area(Graphic *graphic, int src_ul_x, int src_ul_y,
    328 		      int dst_ul_x, int dst_ul_y, unsigned w, unsigned h,
    329 		      unsigned default_color)
    330 {
    331     int sx, ex, dx;
    332     int sy, ey, dy;
    333     int xx, yy;
    334     RegisterNum color;
    335 
    336     if (dst_ul_x <= src_ul_x) {
    337 	sx = 0;
    338 	ex = (int) w - 1;
    339 	dx = +1;
    340     } else {
    341 	sx = (int) w - 1;
    342 	ex = 0;
    343 	dx = -1;
    344     }
    345 
    346     if (dst_ul_y <= src_ul_y) {
    347 	sy = 0;
    348 	ey = (int) h - 1;
    349 	dy = +1;
    350     } else {
    351 	sy = (int) h - 1;
    352 	ey = 0;
    353 	dy = -1;
    354     }
    355 
    356     for (yy = sy; yy != ey + dy; yy += dy) {
    357 	int dst_y = dst_ul_y + yy;
    358 	int src_y = src_ul_y + yy;
    359 	if (dst_y < 0 || dst_y >= (int) graphic->actual_height)
    360 	    continue;
    361 
    362 	for (xx = sx; xx != ex + dx; xx += dx) {
    363 	    int dst_x = dst_ul_x + xx;
    364 	    int src_x = src_ul_x + xx;
    365 	    int cell;
    366 	    if (dst_x < 0 || dst_x >= (int) graphic->actual_width)
    367 		continue;
    368 
    369 	    if (src_x < 0 || src_x >= (int) graphic->actual_width ||
    370 		src_y < 0 || src_y >= (int) graphic->actual_height)
    371 		color = (RegisterNum) default_color;
    372 	    else
    373 		color = graphic->pixels[(unsigned) (src_y *
    374 						    graphic->max_width) +
    375 					(unsigned) src_x];
    376 
    377 	    cell = (int) ((unsigned) (dst_y * graphic->max_width) +
    378 			  (unsigned) dst_x);
    379 	    SetSpixel(graphic, cell, color);
    380 	}
    381     }
    382 }
    383 
    384 #define set_color_register(color_registers, color, pr, pg, pb) \
    385 do { \
    386     assert(color <= MAX_COLOR_REGISTERS); \
    387     { \
    388     ColorRegister *reg = &color_registers[color]; \
    389     reg->r = (short) pr; \
    390     reg->g = (short) pg; \
    391     reg->b = (short) pb; \
    392     } \
    393 } while (0)
    394 
    395 /* Graphics which don't use private colors will act as if they are using a
    396  * device-wide color palette.
    397  */
    398 static void
    399 set_shared_color_register(unsigned color, int r, int g, int b)
    400 {
    401     unsigned ii;
    402 
    403     assert(color < MAX_COLOR_REGISTERS);
    404 
    405     set_color_register(getSharedRegisters(), color, r, g, b);
    406 
    407     if (!used_graphics)
    408 	return;
    409 
    410     FOR_EACH_SLOT(ii) {
    411 	Graphic *graphic;
    412 
    413 	if (!(graphic = getActiveSlot(ii)))
    414 	    continue;
    415 	if (graphic->private_colors)
    416 	    continue;
    417 
    418 	if (graphic->color_registers_used[ii]) {
    419 	    graphic->dirty = True;
    420 	}
    421     }
    422 }
    423 
    424 void
    425 fetch_color_register(Graphic *graphic,
    426 		     unsigned color,
    427 		     ColorRegister *reg)
    428 {
    429     assert(color < MAX_COLOR_REGISTERS);
    430 
    431     reg->r = -1;
    432     reg->g = -1;
    433     reg->b = -1;
    434 
    435     if (graphic->color_registers_used[color]) {
    436 	if (graphic->private_colors) {
    437 	    *reg = graphic->private_color_registers[color];
    438 	} else {
    439 	    *reg = getSharedRegisters()[color];
    440 	}
    441     }
    442 }
    443 
    444 void
    445 update_color_register(Graphic *graphic,
    446 		      unsigned color,
    447 		      int r,
    448 		      int g,
    449 		      int b)
    450 {
    451     assert(color < MAX_COLOR_REGISTERS);
    452 
    453     if (graphic->private_colors) {
    454 	set_color_register(graphic->private_color_registers,
    455 			   color, r, g, b);
    456 	if (graphic->color_registers_used[color]) {
    457 	    graphic->dirty = True;
    458 	}
    459 	graphic->color_registers_used[color] = True;
    460     } else {
    461 	set_shared_color_register(color, r, g, b);
    462     }
    463 }
    464 
    465 #define SQUARE(X) ( (X) * (X) )
    466 
    467 RegisterNum
    468 find_color_register(ColorRegister const *color_registers, int r, int g, int b)
    469 {
    470     unsigned i;
    471     unsigned closest_index;
    472     unsigned closest_distance;
    473 
    474     /* I have no idea what algorithm DEC used for this.
    475      * The documentation warns that it is unpredictable, especially with values
    476      * far away from any allocated color so it is probably a very simple
    477      * heuristic rather than something fancy like finding the minimum distance
    478      * in a linear perceptive color space.
    479      */
    480     closest_index = MAX_COLOR_REGISTERS;
    481     closest_distance = 0U;
    482     for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
    483 	unsigned d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
    484 				 SQUARE(3 * (color_registers[i].g - g)) +
    485 				 SQUARE(1 * (color_registers[i].b - b)));
    486 	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
    487 	    closest_index = i;
    488 	    closest_distance = d;
    489 	}
    490     }
    491 
    492     TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
    493 	   r, g, b,
    494 	   closest_index,
    495 	   closest_distance,
    496 	   color_registers[closest_index].r,
    497 	   color_registers[closest_index].g,
    498 	   color_registers[closest_index].b));
    499     return (RegisterNum) closest_index;
    500 }
    501 
    502 #if OPT_INHERIT_COLORS
    503 static void
    504 copy_color_registers(Graphic *target, Graphic *source)
    505 {
    506     memcpy(target->color_registers_used,
    507 	   source->color_registers_used,
    508 	   sizeof(Boolean) * MAX_COLOR_REGISTERS);
    509 
    510     memcpy(target->private_color_registers,
    511 	   source->color_registers,
    512 	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
    513 }
    514 #endif
    515 
    516 static void
    517 init_color_registers(TScreen const *screen, ColorRegister *color_registers)
    518 {
    519     const int graphics_termid = GraphicsTermId(screen);
    520 
    521     TRACE(("setting initial colors for terminal %d\n", graphics_termid));
    522     memset(color_registers,
    523 	   0,
    524 	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
    525 
    526     /*
    527      * default color registers:
    528      *     (mono) (color)
    529      * VK100/GIGI (fixed)
    530      * VT125:
    531      *   0: 0%      0%
    532      *   1: 33%     blue
    533      *   2: 66%     red
    534      *   3: 100%    green
    535      * VT240:
    536      *   0: 0%      0%
    537      *   1: 33%     blue
    538      *   2: 66%     red
    539      *   3: 100%    green
    540      * VT241:
    541      *   0: 0%      0%
    542      *   1: 33%     blue
    543      *   2: 66%     red
    544      *   3: 100%    green
    545      * VT330:
    546      *   0: 0%      0%              (bg for light on dark mode)
    547      *   1: 33%     blue (red?)
    548      *   2: 66%     red (green?)
    549      *   3: 100%    green (yellow?) (fg for light on dark mode)
    550      * VT340:
    551      *   0: 0%      0%              (bg for light on dark mode)
    552      *   1: 14%     blue
    553      *   2: 29%     red
    554      *   3: 43%     green
    555      *   4: 57%     magenta
    556      *   5: 71%     cyan
    557      *   6: 86%     yellow
    558      *   7: 100%    50%             (fg for light on dark mode)
    559      *   8: 0%      25%
    560      *   9: 14%     gray-blue
    561      *  10: 29%     gray-red
    562      *  11: 43%     gray-green
    563      *  12: 57%     gray-magenta
    564      *  13: 71%     gray-cyan
    565      *  14: 86%     gray-yellow
    566      *  15: 100%    75%             ("white")
    567      * VT382:
    568      *   ? (FIXME: B&W only?)
    569      * dxterm:
    570      *  ?
    571      */
    572     switch (graphics_termid) {
    573     case 125:
    574     case 241:
    575 	set_color_register(color_registers, 0, 0, 0, 0);
    576 	set_color_register(color_registers, 1, 0, 0, 100);
    577 	set_color_register(color_registers, 2, 0, 100, 0);
    578 	set_color_register(color_registers, 3, 100, 0, 0);
    579 	break;
    580     case 240:
    581     case 330:
    582 	set_color_register(color_registers, 0, 0, 0, 0);
    583 	set_color_register(color_registers, 1, 33, 33, 33);
    584 	set_color_register(color_registers, 2, 66, 66, 66);
    585 	set_color_register(color_registers, 3, 100, 100, 100);
    586 	break;
    587     case 340:
    588     default:
    589 	set_color_register(color_registers, 0, 0, 0, 0);
    590 	set_color_register(color_registers, 1, 20, 20, 80);
    591 	set_color_register(color_registers, 2, 80, 13, 13);
    592 	set_color_register(color_registers, 3, 20, 80, 20);
    593 	set_color_register(color_registers, 4, 80, 20, 80);
    594 	set_color_register(color_registers, 5, 20, 80, 80);
    595 	set_color_register(color_registers, 6, 80, 80, 20);
    596 	set_color_register(color_registers, 7, 53, 53, 53);
    597 	set_color_register(color_registers, 8, 26, 26, 26);
    598 	set_color_register(color_registers, 9, 33, 33, 60);
    599 	set_color_register(color_registers, 10, 60, 26, 26);
    600 	set_color_register(color_registers, 11, 33, 60, 33);
    601 	set_color_register(color_registers, 12, 60, 33, 60);
    602 	set_color_register(color_registers, 13, 33, 60, 60);
    603 	set_color_register(color_registers, 14, 60, 60, 33);
    604 	set_color_register(color_registers, 15, 80, 80, 80);
    605 	break;
    606     case 382:			/* FIXME: verify */
    607 	set_color_register(color_registers, 0, 0, 0, 0);
    608 	set_color_register(color_registers, 1, 100, 100, 100);
    609 	break;
    610     }
    611 
    612 #ifdef DEBUG_PALETTE
    613     {
    614 	unsigned i;
    615 
    616 	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
    617 	    TRACE(("initial value for register %03u: %d,%d,%d\n",
    618 		   i,
    619 		   color_registers[i].r,
    620 		   color_registers[i].g,
    621 		   color_registers[i].b));
    622 	}
    623     }
    624 #endif
    625 }
    626 
    627 unsigned
    628 get_color_register_count(TScreen const *screen)
    629 {
    630     unsigned num_color_registers;
    631 
    632     if (screen->numcolorregisters >= 0) {
    633 	num_color_registers = (unsigned) screen->numcolorregisters;
    634     } else {
    635 	num_color_registers = 0U;
    636     }
    637 
    638     if (num_color_registers > 1U) {
    639 	if (num_color_registers > MAX_COLOR_REGISTERS)
    640 	    return MAX_COLOR_REGISTERS;
    641 	return num_color_registers;
    642     }
    643 
    644     /*
    645      * color capabilities:
    646      * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
    647      * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
    648      * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
    649      * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
    650      * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
    651      * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
    652      * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
    653      * dxterm      ?
    654      */
    655     switch (screen->graphics_termid) {
    656     case 125:
    657 	return 4U;
    658     case 240:
    659 	return 4U;
    660     case 241:
    661 	return 4U;
    662     case 330:
    663 	return 4U;
    664     case 340:
    665 	return 16U;
    666     case 382:
    667 	return 2U;
    668     default:
    669 	/* unknown graphics model -- might as well be generous */
    670 	return MAX_COLOR_REGISTERS;
    671     }
    672 }
    673 
    674 static void
    675 init_graphic(TScreen *screen,
    676 	     Graphic *graphic,
    677 	     unsigned type,
    678 	     int charrow,
    679 	     int charcol,
    680 	     unsigned num_color_registers)
    681 {
    682     int private_colors = screen->privatecolorregisters;
    683     const unsigned max_pixels = (unsigned) (graphic->max_width *
    684 					    graphic->max_height);
    685 
    686     TRACE(("init_graphic %u pixels at %d,%d\n", max_pixels, charrow, charcol));
    687 
    688     graphic->hidden = False;
    689     graphic->dirty = True;
    690     memset(graphic->pixels, COLOR_HOLE & 0xff, max_pixels * sizeof(RegisterNum));
    691     memset(graphic->color_registers_used, False, sizeof(graphic->color_registers_used));
    692 
    693     /*
    694      * text and graphics interactions:
    695      * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
    696      * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
    697      * VT382                     text writes on top of graphics buffer FIXME: verify
    698      * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
    699      */
    700 
    701     /*
    702      * dimensions (ReGIS logical, physical):
    703      * VK100/GIGI  768x4??  768x240(status?)
    704      * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
    705      * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
    706      * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
    707      * VT330       800x480  800x480(+?status)
    708      * VT340       800x480  800x480(+?status)
    709      * VT382       960x750  sixel only
    710      * dxterm      ?x? ?x?  variable?
    711      */
    712 
    713     graphic->actual_width = 0;
    714     graphic->actual_height = 0;
    715 
    716     graphic->pixw = 1;
    717     graphic->pixh = 1;
    718 
    719     graphic->valid_registers = num_color_registers;
    720     TRACE(("%d color registers\n", graphic->valid_registers));
    721 
    722     graphic->private_colors = private_colors;
    723     if (graphic->private_colors) {
    724 #if OPT_INHERIT_COLORS
    725 	unsigned ii;
    726 	int max_charrow = -1;
    727 	Graphic *newest = NULL;
    728 #endif
    729 
    730 	TRACE(("using private color registers\n"));
    731 
    732 #if OPT_INHERIT_COLORS
    733 	FOR_EACH_SLOT(ii) {
    734 	    Graphic *check;
    735 	    if (!(check = getActiveSlot(ii)))
    736 		continue;
    737 	    if (!newest || check->charrow >= max_charrow) {
    738 		max_charrow = check->charrow;
    739 		newest = check;
    740 	    }
    741 	}
    742 
    743 	if (newest != NULL && newest != graphic) {
    744 	    copy_color_registers(graphic, newest);
    745 	} else {
    746 	    init_color_registers(screen, graphic->private_color_registers);
    747 	}
    748 #else
    749 	init_color_registers(screen, graphic->private_color_registers);
    750 #endif
    751 	graphic->color_registers = graphic->private_color_registers;
    752     } else {
    753 	TRACE(("using shared color registers\n"));
    754 	graphic->color_registers = getSharedRegisters();
    755     }
    756 
    757     graphic->charrow = charrow;
    758     graphic->charcol = charcol;
    759     graphic->type = type;
    760     graphic->valid = False;
    761 }
    762 
    763 Graphic *
    764 get_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
    765 {
    766     TScreen *screen = TScreenOf(xw);
    767     const int bufferid = screen->whichBuf;
    768     Graphic *graphic = NULL;
    769     unsigned ii;
    770 
    771     TRACE(("get_new_graphic %d,%d type %d\n", charrow, charcol, type));
    772 
    773     FOR_EACH_SLOT(ii) {
    774 	if ((graphic = getInactiveSlot(screen, ii)) != NULL) {
    775 	    TRACE(("using fresh graphic index %u as id %u\n",
    776 		   ii, next_graphic_id));
    777 	    break;
    778 	}
    779     }
    780 
    781     /* if none are free, recycle the graphic scrolled back the farthest */
    782     if (!graphic) {
    783 	int min_charrow = 0;
    784 	Graphic *min_graphic = NULL;
    785 	if_TRACE(unsigned best_ii = (1 + MAX_GRAPHICS));
    786 
    787 	FOR_EACH_SLOT(ii) {
    788 	    if (!(graphic = getActiveSlot(ii)))
    789 		continue;
    790 	    if (!min_graphic || graphic->charrow < min_charrow) {
    791 		if_TRACE(best_ii = ii);
    792 		min_charrow = graphic->charrow;
    793 		min_graphic = graphic;
    794 	    }
    795 	}
    796 	TRACE(("recycling old graphic index %u as id %u\n",
    797 	       best_ii, next_graphic_id));
    798 	graphic = min_graphic;
    799     }
    800 
    801     if (graphic) {
    802 	unsigned num_color_registers;
    803 	num_color_registers = get_color_register_count(screen);
    804 	graphic->xw = xw;
    805 	graphic->bufferid = bufferid;
    806 	graphic->id = next_graphic_id++;
    807 	init_graphic(screen,
    808 		     graphic,
    809 		     type,
    810 		     charrow,
    811 		     charcol,
    812 		     num_color_registers);
    813     }
    814     return graphic;
    815 }
    816 
    817 Graphic *
    818 get_new_or_matching_graphic(XtermWidget xw,
    819 			    int charrow,
    820 			    int charcol,
    821 			    int actual_width,
    822 			    int actual_height,
    823 			    unsigned type)
    824 {
    825     TScreen const *screen = TScreenOf(xw);
    826     const int bufferid = screen->whichBuf;
    827     Graphic *graphic;
    828     unsigned ii;
    829 
    830     FOR_EACH_SLOT(ii) {
    831 	TRACE(("checking slot=%u for graphic at %d,%d %dx%d bufferid=%d type=%u\n", ii,
    832 	       charrow, charcol,
    833 	       actual_width, actual_height,
    834 	       bufferid, type));
    835 	if ((graphic = getActiveSlot(ii)) != NULL) {
    836 	    if (graphic->type == type &&
    837 		graphic->bufferid == bufferid &&
    838 		graphic->charrow == charrow &&
    839 		graphic->charcol == charcol &&
    840 		graphic->actual_width == actual_width &&
    841 		graphic->actual_height == actual_height) {
    842 		TRACE(("found existing graphic slot=%u id=%u\n", ii, graphic->id));
    843 		return graphic;
    844 	    }
    845 	    TRACE(("not a match: graphic at %d,%d %dx%d bufferid=%d type=%u\n",
    846 		   graphic->charrow, graphic->charcol,
    847 		   graphic->actual_width, graphic->actual_height,
    848 		   graphic->bufferid, graphic->type));
    849 	}
    850     }
    851 
    852     /* if no match get a new graphic */
    853     if ((graphic = get_new_graphic(xw, charrow, charcol, type)) != NULL) {
    854 	graphic->actual_width = actual_width;
    855 	graphic->actual_height = actual_height;
    856 	TRACE(("no match; created graphic at %d,%d %dx%d bufferid=%d type=%u\n",
    857 	       graphic->charrow, graphic->charcol,
    858 	       graphic->actual_width, graphic->actual_height,
    859 	       graphic->bufferid, graphic->type));
    860     }
    861     return graphic;
    862 }
    863 
    864 #define ScaleForXColor(s) (unsigned short) ((unsigned long)(s) * MAX_U_COLOR / CHANNEL_MAX)
    865 
    866 static int
    867 save_allocated_color(const ColorRegister *reg, XtermWidget xw, Pixel *pix)
    868 {
    869     unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    870     unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    871     unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    872     XColor xcolor;
    873     AllocatedColorRegister *new_color;
    874 
    875     /* *INDENT-EQLS* */
    876     xcolor.pixel = 0UL;
    877     xcolor.red   = ScaleForXColor(reg->r);
    878     xcolor.green = ScaleForXColor(reg->g);
    879     xcolor.blue  = ScaleForXColor(reg->b);
    880     xcolor.flags = DoRed | DoGreen | DoBlue;
    881 
    882     if (!allocateBestRGB(xw, &xcolor)) {
    883 	TRACE(("unable to allocate xcolor\n"));
    884 	*pix = 0UL;
    885 	return 0;
    886     } else {
    887 	*pix = xcolor.pixel;
    888 
    889 	if (!(new_color = TypeMalloc(AllocatedColorRegister))) {
    890 	    TRACE(("unable to save pixel %lu\n", (unsigned long) *pix));
    891 	    return 0;
    892 	} else {
    893 	    new_color->r = reg->r;
    894 	    new_color->g = reg->g;
    895 	    new_color->b = reg->b;
    896 	    new_color->pix = *pix;
    897 	    new_color->next = allocated_colors[rr][gg][bb];
    898 
    899 	    allocated_colors[rr][gg][bb] = new_color;
    900 
    901 	    return 1;
    902 	}
    903     }
    904 }
    905 
    906 /* FIXME: with so many possible colors we need to determine
    907  * when to free them to be nice to PseudoColor displays
    908  */
    909 static Pixel
    910 color_register_to_xpixel(const ColorRegister *reg, XtermWidget xw)
    911 {
    912     Pixel result;
    913     unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    914     unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    915     unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
    916     const AllocatedColorRegister *search;
    917 
    918     for (search = allocated_colors[rr][gg][bb]; search; search = search->next) {
    919 	if (search->r == reg->r &&
    920 	    search->g == reg->g &&
    921 	    search->b == reg->b) {
    922 	    return search->pix;
    923 	}
    924     }
    925 
    926     save_allocated_color(reg, xw, &result);
    927     return result;
    928 }
    929 
    930 static void
    931 refresh_graphic(TScreen const *screen,
    932 		Graphic const *graphic,
    933 		ColorRegister *buffer,
    934 		int refresh_x,
    935 		int refresh_y,
    936 		int refresh_w,
    937 		int refresh_h,
    938 		int draw_x,
    939 		int draw_y,
    940 		int draw_w,
    941 		int draw_h)
    942 {
    943     int const pw = graphic->pixw;
    944     int const ph = graphic->pixh;
    945     int const graph_x = graphic->charcol * FontWidth(screen);
    946     int const graph_y = graphic->charrow * FontHeight(screen);
    947     int const graph_w = graphic->actual_width;
    948     int const graph_h = graphic->actual_height;
    949     int const mw = graphic->max_width;
    950 
    951     int r, c;
    952     int pmy;
    953     RegisterNum regnum;
    954 
    955     if_TRACE(int holes = 0);
    956     if_TRACE(int total = 0);
    957     if_TRACE(int out_of_range = 0);
    958 
    959     TRACE(("refreshing graphic %u from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d)\n",
    960 	   graphic->id,
    961 	   graph_x, graph_y, draw_w, draw_h,
    962 	   graphic->valid,
    963 	   graphic->actual_width,
    964 	   graphic->actual_height,
    965 	   pw, ph,
    966 	   graphic->max_width,
    967 	   graphic->max_height));
    968 
    969     TRACE(("refresh pixmap starts at %d,%d\n", refresh_x, refresh_y));
    970 
    971     for (r = 0, pmy = graph_y; r < graph_h; r++, pmy += ph) {
    972 	int pmx, buffer_y, pixel_y;
    973 
    974 	if (pmy + ph - 1 < draw_y)
    975 	    continue;
    976 	if (pmy > draw_y + draw_h - 1)
    977 	    break;
    978 
    979 	if (pmy < draw_y ||
    980 	    pmy < refresh_y ||
    981 	    pmy > refresh_y + refresh_h - 1) {
    982 	    if_TRACE(out_of_range++);
    983 	    continue;
    984 	}
    985 	pixel_y = r * mw;
    986 	buffer_y = (pmy - refresh_y) * refresh_w;
    987 
    988 	for (c = 0, pmx = graph_x; c < graph_w; c++, pmx += pw) {
    989 
    990 	    if (pmx + pw - 1 < draw_x)
    991 		continue;
    992 	    if (pmx > draw_x + draw_w - 1)
    993 		break;
    994 
    995 	    if (pmx < draw_x ||
    996 		pmx < refresh_x ||
    997 		pmx > refresh_x + refresh_w - 1) {
    998 		if_TRACE(out_of_range++);
    999 		continue;
   1000 	    }
   1001 
   1002 	    if_TRACE(total++);
   1003 	    regnum = graphic->pixels[pixel_y + c];
   1004 	    if (regnum == COLOR_HOLE) {
   1005 		if_TRACE(holes++);
   1006 	    } else {
   1007 		buffer[buffer_y + (pmx - refresh_x)] =
   1008 		    graphic->color_registers[regnum];
   1009 	    }
   1010 	}
   1011     }
   1012 
   1013     TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes; %d were out of pixmap range\n",
   1014 	   holes, total, out_of_range));
   1015 }
   1016 
   1017 #define MAX_PCT 100.		/* HLS uses this for L, S percentages */
   1018 #define MAX_RGB 100.		/* use this rather than 255 */
   1019 
   1020 /*
   1021  * In HLS, H is an angle, in degrees, and L, S are percentages.
   1022  * Primary color hues:
   1023  *  blue:    0 degrees
   1024  *  red:   120 degrees
   1025  *  green: 240 degrees
   1026  */
   1027 void
   1028 hls2rgb(int h, int l, int s, short *r, short *g, short *b)
   1029 {
   1030     int hs;
   1031     const double lv = l / MAX_PCT;
   1032     const double sv = s / MAX_PCT;
   1033     double c, x, m, c2;
   1034     double r1, g1, b1;
   1035 
   1036     h = h - 120;		/* Rotate so that blue is at 0 degrees  */
   1037     while (h < 0)
   1038 	h += 360;		/* Normalize to 0 to 360, */
   1039     while (h >= 360)
   1040 	h -= 360;
   1041     hs = ((h + 59) / 60) % 6;
   1042 
   1043     if (s == 0) {
   1044 	*r = *g = *b = (short) l;
   1045 	return;
   1046     }
   1047 
   1048     c2 = (2.0 * lv) - 1.0;
   1049     if (c2 < 0.0)
   1050 	c2 = -c2;
   1051     c = (1.0 - c2) * sv;
   1052     x = (hs & 1) ? c : 0.0;
   1053     m = lv - 0.5 * c;
   1054 
   1055     switch (hs) {
   1056     case 0:
   1057 	r1 = c;
   1058 	g1 = x;
   1059 	b1 = 0.0;
   1060 	break;
   1061     case 1:
   1062 	r1 = x;
   1063 	g1 = c;
   1064 	b1 = 0.0;
   1065 	break;
   1066     case 2:
   1067 	r1 = 0.0;
   1068 	g1 = c;
   1069 	b1 = x;
   1070 	break;
   1071     case 3:
   1072 	r1 = 0.0;
   1073 	g1 = x;
   1074 	b1 = c;
   1075 	break;
   1076     case 4:
   1077 	r1 = x;
   1078 	g1 = 0.0;
   1079 	b1 = c;
   1080 	break;
   1081     case 5:
   1082 	r1 = c;
   1083 	g1 = 0.0;
   1084 	b1 = x;
   1085 	break;
   1086     default:
   1087 	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
   1088 	*r = (short) 360;
   1089 	*g = (short) MAX_PCT;
   1090 	*b = (short) MAX_PCT;
   1091 	return;
   1092     }
   1093 
   1094     *r = (short) ((r1 + m) * MAX_PCT + 0.5);
   1095     *g = (short) ((g1 + m) * MAX_PCT + 0.5);
   1096     *b = (short) ((b1 + m) * MAX_PCT + 0.5);
   1097 
   1098     if (*r < 0)
   1099 	*r = 0;
   1100     else if (*r > 100)
   1101 	*r = 100;
   1102     if (*g < 0)
   1103 	*g = 0;
   1104     else if (*g > 100)
   1105 	*g = 100;
   1106     if (*b < 0)
   1107 	*b = 0;
   1108     else if (*b > 100)
   1109 	*b = 100;
   1110 }
   1111 
   1112 void
   1113 rgb2hls(int r, int g, int b, short *h, short *l, short *s)
   1114 {
   1115     const double scaled_r = (r / MAX_RGB);
   1116     const double scaled_g = (g / MAX_RGB);
   1117     const double scaled_b = (b / MAX_RGB);
   1118 
   1119     const double min_scale = Min(Min(scaled_r, scaled_g), scaled_b);
   1120     const double max_scale = Max(Max(scaled_r, scaled_g), scaled_b);
   1121     const double dif_scale = max_scale - min_scale;
   1122 
   1123     double h_work = 0.;
   1124     double s_work = 0.;
   1125     double l_work = ((max_scale + min_scale) / 2.);
   1126 
   1127     if (dif_scale != 0.) {
   1128 	if (l_work < 0.5f) {
   1129 	    s_work = (dif_scale / (max_scale + min_scale));
   1130 	} else {
   1131 	    s_work = (dif_scale / (2. - max_scale - min_scale));
   1132 	}
   1133 
   1134 	if (scaled_r == max_scale) {
   1135 	    h_work = (scaled_g - scaled_b) / dif_scale;
   1136 	} else if (scaled_g == max_scale) {
   1137 	    h_work = 2. + (scaled_b - scaled_r) / dif_scale;
   1138 	} else if (scaled_b == max_scale) {
   1139 	    h_work = 4. + (scaled_r - scaled_g) / dif_scale;
   1140 	}
   1141     }
   1142 
   1143     h_work *= 60.;
   1144     if (h_work < 0)
   1145 	h_work += 360.;
   1146 
   1147     s_work *= MAX_RGB;
   1148     l_work *= MAX_RGB;
   1149 
   1150     *h = (short) h_work;
   1151     *s = (short) s_work;
   1152     *l = (short) l_work;
   1153 }
   1154 
   1155 void
   1156 dump_graphic(Graphic const *graphic)
   1157 {
   1158 #if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
   1159     RegisterNum color;
   1160 #endif
   1161 #ifdef DUMP_BITMAP
   1162     int r, c;
   1163     ColorRegister const *reg;
   1164 #endif
   1165 
   1166     (void) graphic;
   1167 
   1168     TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
   1169 	   graphic->id,
   1170 	   graphic->charrow,
   1171 	   graphic->charcol,
   1172 	   graphic->actual_width,
   1173 	   graphic->actual_height,
   1174 	   graphic->pixw,
   1175 	   graphic->pixh));
   1176 
   1177 #ifdef DUMP_COLORS
   1178     TRACE(("graphic colors:\n"));
   1179     for (color = 0; color < graphic->valid_registers; color++) {
   1180 	TRACE(("%03u: %d,%d,%d\n",
   1181 	       color,
   1182 	       graphic->color_registers[color].r,
   1183 	       graphic->color_registers[color].g,
   1184 	       graphic->color_registers[color].b));
   1185     }
   1186 #endif
   1187 
   1188 #ifdef DUMP_BITMAP
   1189     TRACE(("graphic pixels:\n"));
   1190     for (r = 0; r < graphic->actual_height; r++) {
   1191 	for (c = 0; c < graphic->actual_width; c++) {
   1192 	    color = graphic->pixels[r * graphic->max_width + c];
   1193 	    if (color == COLOR_HOLE) {
   1194 		TRACE(("?"));
   1195 	    } else {
   1196 		reg = &graphic->color_registers[color];
   1197 		if (reg->r + reg->g + reg->b > 200) {
   1198 		    TRACE(("#"));
   1199 		} else if (reg->r + reg->g + reg->b > 150) {
   1200 		    TRACE(("%%"));
   1201 		} else if (reg->r + reg->g + reg->b > 100) {
   1202 		    TRACE((":"));
   1203 		} else if (reg->r + reg->g + reg->b > 80) {
   1204 		    TRACE(("."));
   1205 		} else {
   1206 		    TRACE((" "));
   1207 		}
   1208 	    }
   1209 	}
   1210 	TRACE(("\n"));
   1211     }
   1212 
   1213     TRACE(("\n"));
   1214 #endif
   1215 }
   1216 
   1217 /* Erase the portion of any displayed graphic overlapping with a rectangle
   1218  * of the given size and location in pixels relative to the start of the
   1219  * graphic.  This is used to allow text to "erase" graphics underneath it.
   1220  */
   1221 static void
   1222 erase_graphic(Graphic *graphic, int x, int y, int w, int h)
   1223 {
   1224     const int pw = graphic->pixw;
   1225     const int ph = graphic->pixh;
   1226     const int r_min = y - ph + 1;
   1227     const int r_max = y + h - 1;
   1228     const int c_min = x - pw + 1;
   1229     const int c_max = x + w - 1;
   1230 
   1231     int r;
   1232     int rbase = 0;
   1233 
   1234     TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
   1235 
   1236     for (r = 0; r < graphic->actual_height; r++) {
   1237 	if (rbase >= r_min
   1238 	    && rbase <= r_max) {
   1239 	    int c;
   1240 	    int cbase = 0;
   1241 	    for (c = 0; c < graphic->actual_width; c++) {
   1242 		if (cbase >= c_min
   1243 		    && cbase <= c_max) {
   1244 		    const int cell = r * graphic->max_width + c;
   1245 		    ClrSpixel(graphic, cell);
   1246 		}
   1247 		cbase += pw;
   1248 	    }
   1249 	}
   1250 	rbase += ph;
   1251     }
   1252 }
   1253 
   1254 static int
   1255 compare_graphic_ids(const void *left, const void *right)
   1256 {
   1257     const Graphic *l = *(const Graphic *const *) left;
   1258     const Graphic *r = *(const Graphic *const *) right;
   1259 
   1260     if (!l->valid || !r->valid)
   1261 	return 0;
   1262 
   1263     if (l->bufferid < r->bufferid)
   1264 	return -1;
   1265     else if (l->bufferid > r->bufferid)
   1266 	return 1;
   1267 
   1268     if (l->id < r->id)
   1269 	return -1;
   1270     else
   1271 	return 1;
   1272 }
   1273 
   1274 static void
   1275 clip_area(int *orig_x, int *orig_y, int *orig_w, int *orig_h,
   1276 	  int clip_x, int clip_y, int clip_w, int clip_h)
   1277 {
   1278     if (*orig_x < clip_x) {
   1279 	const int diff = clip_x - *orig_x;
   1280 	*orig_x += diff;
   1281 	*orig_w -= diff;
   1282     }
   1283     if (*orig_w > 0 && *orig_x + *orig_w > clip_x + clip_w) {
   1284 	*orig_w -= (*orig_x + *orig_w) - (clip_x + clip_w);
   1285     }
   1286 
   1287     if (*orig_y < clip_y) {
   1288 	const int diff = clip_y - *orig_y;
   1289 	*orig_y += diff;
   1290 	*orig_h -= diff;
   1291     }
   1292     if (*orig_h > 0 && *orig_y + *orig_h > clip_y + clip_h) {
   1293 	*orig_h -= (*orig_y + *orig_h) - (clip_y + clip_h);
   1294     }
   1295 }
   1296 
   1297 static Bool
   1298 GetGraphicsOrder(TScreen *screen,
   1299 		 int skip_clean,
   1300 		 Graphic *ordered_graphics[MAX_GRAPHICS],
   1301 		 unsigned *resultp)
   1302 {
   1303     unsigned ii;
   1304     unsigned active_count;
   1305 
   1306     *resultp = active_count = 0;
   1307     FOR_EACH_SLOT(ii) {
   1308 	Graphic *graphic;
   1309 	if (!(graphic = getActiveSlot(ii)))
   1310 	    continue;
   1311 	TRACE(("refreshing graphic %d on buffer %d, current buffer %d\n",
   1312 	       graphic->id, graphic->bufferid, screen->whichBuf));
   1313 	if (screen->whichBuf == 0) {
   1314 	    if (graphic->bufferid != 0) {
   1315 		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d\n",
   1316 		       graphic->id, graphic->bufferid, screen->whichBuf));
   1317 		continue;
   1318 	    }
   1319 	} else {
   1320 	    if (graphic->bufferid == 0 && graphic->charrow >= 0) {
   1321 		TRACE(("skipping graphic %d from normal buffer (%d) when drawing screen=%d because it is not in scrollback area\n",
   1322 		       graphic->id, graphic->bufferid, screen->whichBuf));
   1323 		continue;
   1324 	    }
   1325 	    if (graphic->bufferid == 1 &&
   1326 		graphic->charrow + (graphic->actual_height +
   1327 				    FontHeight(screen) - 1) /
   1328 		FontHeight(screen) < 0) {
   1329 		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d because it is completely in scrollback area\n",
   1330 		       graphic->id, graphic->bufferid, screen->whichBuf));
   1331 		continue;
   1332 	    }
   1333 	}
   1334 	if (graphic->hidden)
   1335 	    continue;
   1336 	ordered_graphics[active_count++] = graphic;
   1337     }
   1338 
   1339     if (active_count == 0)
   1340 	return False;
   1341     if (active_count > 1) {
   1342 	qsort(ordered_graphics,
   1343 	      (size_t) active_count,
   1344 	      sizeof(ordered_graphics[0]),
   1345 	      compare_graphic_ids);
   1346     }
   1347 
   1348     if (skip_clean) {
   1349 	unsigned jj;
   1350 	unsigned skip_count;
   1351 
   1352 	for (jj = 0; jj < active_count; ++jj) {
   1353 	    if (ordered_graphics[jj]->dirty)
   1354 		break;
   1355 	}
   1356 	skip_count = jj;
   1357 	if (skip_count == active_count)
   1358 	    return False;
   1359 
   1360 	active_count -= skip_count;
   1361 	for (jj = 0; jj < active_count; ++jj) {
   1362 	    ordered_graphics[jj] = ordered_graphics[jj + skip_count];
   1363 	}
   1364     }
   1365     *resultp = active_count;
   1366     return True;
   1367 }
   1368 
   1369 static ColorRegister *
   1370 AllocGraphicsBuffer(TScreen *screen,
   1371 		    int ncols,
   1372 		    int nrows)
   1373 {
   1374     int xx, yy;
   1375     int const refresh_w = ncols * FontWidth(screen);
   1376     int const refresh_h = nrows * FontHeight(screen);
   1377     ColorRegister *buffer;
   1378 
   1379     if (!(buffer = TypeMallocN(ColorRegister,
   1380 			         (unsigned) refresh_w * (unsigned) refresh_h))) {
   1381 	TRACE(("unable to allocate %dx%d buffer for graphics refresh\n",
   1382 	       refresh_w, refresh_h));
   1383     } else {
   1384 	/* assuming two's complement, the memset will be much faster than loop */
   1385 	if ((unsigned short) null_color.r == 0xffff) {
   1386 	    memset(buffer, 0xff,
   1387 		   sizeof(ColorRegister) * (size_t) (refresh_h * refresh_w));
   1388 	} else {
   1389 	    for (yy = 0; yy < refresh_h; yy++) {
   1390 		for (xx = 0; xx < refresh_w; xx++) {
   1391 		    buffer[yy * refresh_w + xx] = null_color;
   1392 		}
   1393 	    }
   1394 	}
   1395     }
   1396     return buffer;
   1397 }
   1398 
   1399 typedef struct {
   1400     int x_min;
   1401     int x_max;
   1402     int y_min;
   1403     int y_max;
   1404 } ClipLimits;
   1405 
   1406 static Boolean
   1407 RefreshClipped(TScreen *screen,
   1408 	       int leftcol,
   1409 	       int toprow,
   1410 	       int ncols,
   1411 	       int nrows,
   1412 	       Graphic *ordered_graphics[MAX_GRAPHICS],
   1413 	       unsigned active_count,
   1414 	       ColorRegister *buffer,
   1415 	       ClipLimits * result)
   1416 {
   1417     int const scroll_y = screen->topline * FontHeight(screen);
   1418     int const refresh_x = leftcol * FontWidth(screen);
   1419     int const refresh_y = toprow * FontHeight(screen) + scroll_y;
   1420     int const refresh_w = ncols * FontWidth(screen);
   1421     int const refresh_h = nrows * FontHeight(screen);
   1422     ClipLimits my_limits;
   1423     unsigned jj;
   1424 
   1425     int const altarea_x = 0;
   1426     int const altarea_y = 0;
   1427     int const altarea_w = Width(screen) * FontWidth(screen);
   1428     int const altarea_h = Height(screen) * FontHeight(screen);
   1429 
   1430     int const scrollarea_x = 0;
   1431     int const scrollarea_y = scroll_y;
   1432     int const scrollarea_w = Width(screen) * FontWidth(screen);
   1433     int const scrollarea_h = -scroll_y;
   1434 
   1435     int const mainarea_x = 0;
   1436     int const mainarea_y = scroll_y;
   1437     int const mainarea_w = Width(screen) * FontWidth(screen);
   1438     int const mainarea_h = -scroll_y + Height(screen) * FontHeight(screen);
   1439 
   1440     my_limits.x_min = refresh_x + refresh_w;
   1441     my_limits.x_max = refresh_x - 1;
   1442     my_limits.y_min = refresh_y + refresh_h;
   1443     my_limits.y_max = refresh_y - 1;
   1444     for (jj = 0; jj < active_count; ++jj) {
   1445 	Graphic *graphic = ordered_graphics[jj];
   1446 	int draw_x = graphic->charcol * FontWidth(screen);
   1447 	int draw_y = graphic->charrow * FontHeight(screen);
   1448 	int draw_w = graphic->actual_width;
   1449 	int draw_h = graphic->actual_height;
   1450 
   1451 	if (screen->whichBuf != 0) {
   1452 	    if (graphic->bufferid != 0) {
   1453 		/* clip to alt buffer */
   1454 		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
   1455 			  altarea_x, altarea_y, altarea_w, altarea_h);
   1456 	    } else {
   1457 		/* clip to scrollback area */
   1458 		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
   1459 			  scrollarea_x, scrollarea_y,
   1460 			  scrollarea_w, scrollarea_h);
   1461 	    }
   1462 	} else {
   1463 	    /* clip to scrollback + normal area */
   1464 	    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
   1465 		      mainarea_x, mainarea_y,
   1466 		      mainarea_w, mainarea_h);
   1467 	}
   1468 
   1469 	clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
   1470 		  refresh_x, refresh_y, refresh_w, refresh_h);
   1471 
   1472 	TRACE(("refresh: graph=%u\n", jj));
   1473 	TRACE(("         refresh_x=%d refresh_y=%d refresh_w=%d refresh_h=%d\n",
   1474 	       refresh_x, refresh_y, refresh_w, refresh_h));
   1475 	TRACE(("         draw_x=%d draw_y=%d draw_w=%d draw_h=%d\n",
   1476 	       draw_x, draw_y, draw_w, draw_h));
   1477 
   1478 	if (draw_w > 0 && draw_h > 0) {
   1479 	    refresh_graphic(screen, graphic, buffer,
   1480 			    refresh_x, refresh_y,
   1481 			    refresh_w, refresh_h,
   1482 			    draw_x, draw_y,
   1483 			    draw_w, draw_h);
   1484 	    if (draw_x < my_limits.x_min)
   1485 		my_limits.x_min = draw_x;
   1486 	    if (draw_x + draw_w - 1 > my_limits.x_max)
   1487 		my_limits.x_max = draw_x + draw_w - 1;
   1488 	    if (draw_y < my_limits.y_min)
   1489 		my_limits.y_min = draw_y;
   1490 	    if (draw_y + draw_h - 1 > my_limits.y_max)
   1491 		my_limits.y_max = draw_y + draw_h - 1;
   1492 	}
   1493 	graphic->dirty = False;
   1494     }
   1495 
   1496     if (my_limits.x_max < refresh_x ||
   1497 	my_limits.x_min > refresh_x + refresh_w - 1 ||
   1498 	my_limits.y_max < refresh_y ||
   1499 	my_limits.y_min > refresh_y + refresh_h - 1) {
   1500 	return False;
   1501     }
   1502     *result = my_limits;
   1503     return True;
   1504 }
   1505 
   1506 static Boolean
   1507 FindGraphicHoles(int refresh_x,
   1508 		 int refresh_y,
   1509 		 int refresh_w,
   1510 		 ColorRegister *buffer,
   1511 		 ClipLimits * limits,
   1512 		 unsigned *result)
   1513 {
   1514     const int y_min = limits->y_min - refresh_y;
   1515     const int y_max = limits->y_max - refresh_y;
   1516     const int x_min = limits->x_min - refresh_x;
   1517     const int x_max = limits->x_max - refresh_x;
   1518     const ColorRegister *base = buffer + (y_min * refresh_w);
   1519     int xx, yy;
   1520 
   1521     unsigned holes = 0U;
   1522     unsigned non_holes = 0U;
   1523 
   1524     for (yy = y_min; yy <= y_max; yy++) {
   1525 	const ColorRegister *scan = base + x_min;
   1526 	for (xx = x_min; xx <= x_max; xx++) {
   1527 	    if (scan->r < 0 || scan->g < 0 || scan->b < 0) {
   1528 		holes++;
   1529 	    } else {
   1530 		non_holes++;
   1531 	    }
   1532 	    if (non_holes && holes)
   1533 		goto finish;
   1534 	    ++scan;
   1535 	}
   1536 	base += refresh_w;
   1537     }
   1538 
   1539   finish:
   1540     *result = holes;
   1541     return (non_holes != 0);
   1542 }
   1543 
   1544 /* the coordinates are relative to the screen */
   1545 static void
   1546 refresh_graphics(XtermWidget xw,
   1547 		 int leftcol,
   1548 		 int toprow,
   1549 		 int ncols,
   1550 		 int nrows,
   1551 		 int skip_clean)
   1552 {
   1553     TScreen *const screen = TScreenOf(xw);
   1554     Display *const display = screen->display;
   1555     Window const drawable = VDrawable(screen);
   1556     int const scroll_y = screen->topline * FontHeight(screen);
   1557     int const refresh_x = leftcol * FontWidth(screen);
   1558     int const refresh_y = toprow * FontHeight(screen) + scroll_y;
   1559     int const refresh_w = ncols * FontWidth(screen);
   1560 
   1561     Graphic *ordered_graphics[MAX_GRAPHICS];
   1562     unsigned active_count;
   1563     unsigned holes;
   1564     int xx, yy;
   1565 
   1566     ColorRegister *buffer;
   1567     ClipLimits clip_limits;
   1568 
   1569     if_TRACE(int const refresh_h = nrows * FontHeight(screen));
   1570 
   1571     if (!GetGraphicsOrder(screen, skip_clean, ordered_graphics, &active_count))
   1572 	return;
   1573 
   1574     if (!valid_graphics) {
   1575 	memset(&xgcv, 0, sizeof(xgcv));
   1576 	xgcv.graphics_exposures = False;
   1577 	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
   1578 	last_color = null_color;
   1579 	gc_color = null_color;
   1580 	if (graphics_gc == NULL) {
   1581 	    TRACE(("unable to allocate GC for graphics refresh\n"));
   1582 	    valid_graphics = -1;
   1583 	} else {
   1584 	    valid_graphics = 1;
   1585 	}
   1586     }
   1587     if (valid_graphics < 0)
   1588 	return;
   1589 
   1590     if ((buffer = AllocGraphicsBuffer(screen, ncols, nrows)) == NULL)
   1591 	return;
   1592 
   1593     TRACE(("refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d (%d,%d %dx%d)\n",
   1594 	   screen->topline,
   1595 	   leftcol, toprow,
   1596 	   nrows, ncols,
   1597 	   refresh_x, refresh_y,
   1598 	   refresh_w, refresh_h));
   1599 
   1600     if (!RefreshClipped(screen, leftcol, toprow, ncols, nrows,
   1601 			ordered_graphics,
   1602 			active_count,
   1603 			buffer,
   1604 			&clip_limits)) {
   1605 	free(buffer);
   1606 	return;
   1607     }
   1608 
   1609     if (!FindGraphicHoles(refresh_x,
   1610 			  refresh_y,
   1611 			  refresh_w,
   1612 			  buffer,
   1613 			  &clip_limits,
   1614 			  &holes)) {
   1615 	TRACE(("refresh: visible graphics areas are erased; nothing to do\n"));
   1616 	free(buffer);
   1617 	return;
   1618     }
   1619 
   1620     /*
   1621      * If we have any holes we can't just copy an image rectangle, and masking
   1622      * with bitmaps is very expensive.  This fallback is surprisingly faster
   1623      * than the XPutImage version in some cases, but I don't know why.
   1624      * (This is even though there's no X11 primitive for drawing a horizontal
   1625      * line of height one and no attempt is made to handle multiple lines at
   1626      * once.)
   1627      */
   1628     if (holes > 0U) {
   1629 	int run;
   1630 
   1631 	run = 0;
   1632 	for (yy = clip_limits.y_min - refresh_y;
   1633 	     yy <= clip_limits.y_max - refresh_y;
   1634 	     yy++) {
   1635 	    for (xx = clip_limits.x_min - refresh_x;
   1636 		 xx <= clip_limits.x_max - refresh_x;
   1637 		 xx++) {
   1638 		const ColorRegister color = buffer[yy * refresh_w + xx];
   1639 
   1640 		if (color.r < 0 || color.g < 0 || color.b < 0) {
   1641 		    last_color = color;
   1642 		    if (run > 0) {
   1643 			XDrawLine(display, drawable, graphics_gc,
   1644 				  OriginX(screen) + refresh_x + xx - run,
   1645 				  (OriginY(screen) - scroll_y) + refresh_y + yy,
   1646 				  OriginX(screen) + refresh_x + xx - 1,
   1647 				  (OriginY(screen) - scroll_y) + refresh_y + yy);
   1648 			run = 0;
   1649 		    }
   1650 		    continue;
   1651 		}
   1652 
   1653 		if (DiffColor(color, last_color)) {
   1654 		    last_color = color;
   1655 		    if (run > 0) {
   1656 			XDrawLine(display, drawable, graphics_gc,
   1657 				  OriginX(screen) + refresh_x + xx - run,
   1658 				  (OriginY(screen) - scroll_y) + refresh_y + yy,
   1659 				  OriginX(screen) + refresh_x + xx - 1,
   1660 				  (OriginY(screen) - scroll_y) + refresh_y + yy);
   1661 			run = 0;
   1662 		    }
   1663 
   1664 		    if (DiffColor(color, gc_color)) {
   1665 			xgcv.foreground =
   1666 			    color_register_to_xpixel(&color, xw);
   1667 			XChangeGC(display, graphics_gc, GCForeground, &xgcv);
   1668 			gc_color = color;
   1669 		    }
   1670 		}
   1671 		run++;
   1672 	    }
   1673 	    if (run > 0) {
   1674 		last_color = null_color;
   1675 		XDrawLine(display, drawable, graphics_gc,
   1676 			  OriginX(screen) + refresh_x + xx - run,
   1677 			  (OriginY(screen) - scroll_y) + refresh_y + yy,
   1678 			  OriginX(screen) + refresh_x + xx - 1,
   1679 			  (OriginY(screen) - scroll_y) + refresh_y + yy);
   1680 		run = 0;
   1681 	    }
   1682 	}
   1683     } else {
   1684 	ColorRegister old_colors[2];
   1685 	Pixel fg, old_result[2];
   1686 	XImage *image;
   1687 	char *imgdata;
   1688 	const unsigned image_w = ((unsigned) clip_limits.x_max + 1U -
   1689 				  (unsigned) clip_limits.x_min);
   1690 	const unsigned image_h = ((unsigned) clip_limits.y_max + 1U -
   1691 				  (unsigned) clip_limits.y_min);
   1692 	int nn;
   1693 
   1694 	image = XCreateImage(display, xw->visInfo->visual,
   1695 			     (unsigned) xw->visInfo->depth,
   1696 			     ZPixmap, 0, NULL,
   1697 			     image_w, image_h,
   1698 			     (int) (sizeof(int) * 8U), 0);
   1699 	if (!image) {
   1700 	    TRACE(("unable to allocate XImage for graphics refresh\n"));
   1701 	    free(buffer);
   1702 	    return;
   1703 	}
   1704 	imgdata = TypeMallocN(char, (size_t)(image_h * (unsigned)image->bytes_per_line));
   1705 	if (!imgdata) {
   1706 	    TRACE(("unable to allocate XImage for graphics refresh\n"));
   1707 	    XDestroyImage(image);
   1708 	    free(buffer);
   1709 	    return;
   1710 	}
   1711 	image->data = imgdata;
   1712 
   1713 	fg = 0U;
   1714 	nn = 0;
   1715 
   1716 	/* two-level cache cuts down on lookup-calls */
   1717 	old_result[0] = 0U;
   1718 	old_result[1] = 0U;
   1719 	old_colors[0] = null_color;
   1720 	old_colors[1] = null_color;
   1721 
   1722 	for (yy = clip_limits.y_min - refresh_y;
   1723 	     yy <= clip_limits.y_max - refresh_y;
   1724 	     yy++) {
   1725 	    for (xx = clip_limits.x_min - refresh_x;
   1726 		 xx <= clip_limits.x_max - refresh_x;
   1727 		 xx++) {
   1728 		const ColorRegister color = buffer[yy * refresh_w + xx];
   1729 
   1730 		if (DiffColor(color, old_colors[nn])) {
   1731 		    if (DiffColor(color, old_colors[!nn])) {
   1732 			nn = !nn;
   1733 			fg = color_register_to_xpixel(&color, xw);
   1734 			old_result[nn] = fg;
   1735 			old_colors[nn] = color;
   1736 		    } else {
   1737 			nn = !nn;
   1738 			fg = old_result[nn];
   1739 		    }
   1740 		}
   1741 
   1742 		XPutPixel(image,
   1743 			  xx + refresh_x - clip_limits.x_min,
   1744 			  yy + refresh_y - clip_limits.y_min, fg);
   1745 	    }
   1746 	}
   1747 
   1748 	XPutImage(display, drawable, graphics_gc, image,
   1749 		  0, 0,
   1750 		  OriginX(screen) + clip_limits.x_min,
   1751 		  (OriginY(screen) - scroll_y) + clip_limits.y_min,
   1752 		  image_w, image_h);
   1753 	free(imgdata);
   1754 	image->data = NULL;
   1755 	XDestroyImage(image);
   1756     }
   1757 
   1758     free(buffer);
   1759     XFlush(display);
   1760 }
   1761 
   1762 void
   1763 refresh_displayed_graphics(XtermWidget xw,
   1764 			   int leftcol,
   1765 			   int toprow,
   1766 			   int ncols,
   1767 			   int nrows)
   1768 {
   1769     refresh_graphics(xw, leftcol, toprow, ncols, nrows, 0);
   1770 }
   1771 
   1772 void
   1773 refresh_modified_displayed_graphics(XtermWidget xw)
   1774 {
   1775     TScreen const *screen = TScreenOf(xw);
   1776     refresh_graphics(xw, 0, 0, MaxCols(screen), MaxRows(screen), 1);
   1777 }
   1778 
   1779 void
   1780 scroll_displayed_graphics(XtermWidget xw, int rows)
   1781 {
   1782     if (used_graphics) {
   1783 	TScreen const *screen = TScreenOf(xw);
   1784 	unsigned ii;
   1785 
   1786 	TRACE(("graphics scroll: moving all up %d rows\n", rows));
   1787 	/* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
   1788 
   1789 	FOR_EACH_SLOT(ii) {
   1790 	    Graphic *graphic;
   1791 
   1792 	    if (!(graphic = getActiveSlot(ii)))
   1793 		continue;
   1794 	    if (graphic->bufferid != screen->whichBuf)
   1795 		continue;
   1796 	    if (graphic->hidden)
   1797 		continue;
   1798 
   1799 	    graphic->charrow -= rows;
   1800 	}
   1801     }
   1802 }
   1803 
   1804 void
   1805 pixelarea_clear_displayed_graphics(TScreen const *screen,
   1806 				   int winx,
   1807 				   int winy,
   1808 				   int w,
   1809 				   int h)
   1810 {
   1811     unsigned ii;
   1812 
   1813     if (!used_graphics)
   1814 	return;
   1815 
   1816     FOR_EACH_SLOT(ii) {
   1817 	Graphic *graphic;
   1818 	/* FIXME: are these coordinates (scrolled) screen-relative? */
   1819 	int const scroll_y = (screen->whichBuf == 0
   1820 			      ? screen->topline * FontHeight(screen)
   1821 			      : 0);
   1822 	int graph_x;
   1823 	int graph_y;
   1824 	int x, y;
   1825 
   1826 	if (!(graphic = getActiveSlot(ii)))
   1827 	    continue;
   1828 	if (graphic->bufferid != screen->whichBuf)
   1829 	    continue;
   1830 	if (graphic->hidden)
   1831 	    continue;
   1832 
   1833 	graph_x = graphic->charcol * FontWidth(screen);
   1834 	graph_y = graphic->charrow * FontHeight(screen);
   1835 	x = winx - graph_x;
   1836 	y = (winy - scroll_y) - graph_y;
   1837 
   1838 	TRACE(("pixelarea clear graphics: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
   1839 	       screen->topline,
   1840 	       winx, winy,
   1841 	       w, h,
   1842 	       x, y));
   1843 	erase_graphic(graphic, x, y, w, h);
   1844     }
   1845 }
   1846 
   1847 void
   1848 chararea_clear_displayed_graphics(TScreen const *screen,
   1849 				  int leftcol,
   1850 				  int toprow,
   1851 				  int ncols,
   1852 				  int nrows)
   1853 {
   1854     if (used_graphics) {
   1855 	int const x = leftcol * FontWidth(screen);
   1856 	int const y = toprow * FontHeight(screen);
   1857 	int const w = ncols * FontWidth(screen);
   1858 	int const h = nrows * FontHeight(screen);
   1859 
   1860 	TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
   1861 	       screen->topline,
   1862 	       leftcol, toprow,
   1863 	       nrows, ncols,
   1864 	       x, y, w, h));
   1865 	pixelarea_clear_displayed_graphics(screen, x, y, w, h);
   1866     }
   1867 }
   1868 
   1869 void
   1870 reset_displayed_graphics(TScreen const *screen)
   1871 {
   1872     init_color_registers(screen, getSharedRegisters());
   1873 
   1874     if (used_graphics) {
   1875 	unsigned ii;
   1876 
   1877 	TRACE(("resetting all graphics\n"));
   1878 	FOR_EACH_SLOT(ii) {
   1879 	    deactivateSlot(ii);
   1880 	}
   1881 #if OPT_REGIS_GRAPHICS
   1882 	reset_regis();
   1883 #endif
   1884     }
   1885 }
   1886 
   1887 #ifdef NO_LEAKS
   1888 void
   1889 noleaks_graphics(Display *dpy)
   1890 {
   1891     unsigned ii;
   1892 
   1893     FOR_EACH_SLOT(ii) {
   1894 	deactivateSlot(ii);
   1895     }
   1896     if (valid_graphics > 0)
   1897 	XFreeGC(dpy, graphics_gc);
   1898 }
   1899 #endif
   1900