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