graphics.c revision a5ae21e4
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 138typedef 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 145static AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH]; 146 147#define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++) 148 149static ColorRegister *shared_color_registers; 150static Graphic *displayed_graphics[MAX_GRAPHICS]; 151static unsigned next_graphic_id = 0U; 152static 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 159static ColorRegister null_color = 160{-1, -1, -1}; 161 162static ColorRegister * 163allocRegisters(void) 164{ 165 return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS); 166} 167 168static Graphic * 169freeGraphic(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 179static Graphic * 180allocGraphic(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 203static Graphic * 204getInactiveSlot(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 219static ColorRegister * 220getSharedRegisters(void) 221{ 222 if (!shared_color_registers) 223 shared_color_registers = allocRegisters(); 224 return shared_color_registers; 225} 226 227static void 228deactivateSlot(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 236extern RegisterNum 237read_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 252void 253draw_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 278void 279draw_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 */ 314void 315draw_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 376void 377copy_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 432static void 433set_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 */ 448static void 449set_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 474void 475update_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 497RegisterNum 498find_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 532static void 533init_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 645unsigned 646get_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 692static void 693init_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 758Graphic * 759get_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 808Graphic * 809get_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 857static int 858save_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 */ 898static Pixel 899color_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 919static void 920refresh_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 1006static void 1007outline_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 */ 1082void 1083hls2rgb(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 1160void 1161dump_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 */ 1226static void 1227erase_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 1256static int 1257compare_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 1276static void 1277clip_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 */ 1300static void 1301refresh_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 1681void 1682refresh_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 1691void 1692refresh_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 1698void 1699scroll_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 1723void 1724pixelarea_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 1766void 1767chararea_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 1788void 1789reset_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 1804void 1805noleaks_graphics(void) 1806{ 1807 unsigned ii; 1808 1809 FOR_EACH_SLOT(ii) { 1810 deactivateSlot(ii); 1811 } 1812} 1813#endif 1814