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