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