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