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