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