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