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