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