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