"Fossies" - the Fresh Open Source Software Archive

Member "xterm-379/graphics.c" (16 May 2022, 48122 Bytes) of package /linux/misc/xterm-379.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "graphics.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 372_vs_373.

    1 /* $XTermId: graphics.c,v 1.118 2022/05/16 23:35:50 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 
  138 typedef struct allocated_color_register {
  139     struct allocated_color_register *next;
  140     Pixel pix;
  141     short r, g, b;
  142 } AllocatedColorRegister;
  143 
  144 #define LOOKUP_WIDTH 16
  145 static AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
  146 
  147 #define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
  148 
  149 static ColorRegister *shared_color_registers;
  150 static Graphic *displayed_graphics[MAX_GRAPHICS];
  151 static unsigned next_graphic_id = 0U;
  152 static unsigned used_graphics;  /* 0 to MAX_GRAPHICS */
  153 
  154 static int valid_graphics;
  155 static GC graphics_gc;
  156 static XGCValues xgcv;
  157 static ColorRegister last_color;
  158 static 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 
  165 static ColorRegister null_color =
  166 {-1, -1, -1};
  167 
  168 static ColorRegister *
  169 allocRegisters(void)
  170 {
  171     return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
  172 }
  173 
  174 static Graphic *
  175 freeGraphic(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 
  185 static Graphic *
  186 allocGraphic(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 
  209 static Graphic *
  210 getInactiveSlot(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 
  225 static ColorRegister *
  226 getSharedRegisters(void)
  227 {
  228     if (!shared_color_registers)
  229     shared_color_registers = allocRegisters();
  230     return shared_color_registers;
  231 }
  232 
  233 static void
  234 deactivateSlot(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 
  242 extern RegisterNum
  243 read_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 
  259 void
  260 draw_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 
  285 void
  286 draw_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 
  320 void
  321 copy_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) \
  379 do { \
  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  */
  389 static void
  390 set_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 
  415 void
  416 update_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 
  438 RegisterNum
  439 find_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 
  473 static void
  474 init_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 
  586 unsigned
  587 get_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 
  633 static void
  634 init_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 
  697 Graphic *
  698 get_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 
  747 Graphic *
  748 get_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 
  796 static int
  797 save_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 = TypeMalloc(AllocatedColorRegister))) {
  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  */
  839 static Pixel
  840 color_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 
  860 static void
  861 refresh_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  */
  953 void
  954 hls2rgb(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 
 1031 void
 1032 dump_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  */
 1097 static void
 1098 erase_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 
 1130 static int
 1131 compare_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 
 1150 static void
 1151 clip_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 
 1173 static Bool
 1174 GetGraphicsOrder(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 
 1245 static ColorRegister *
 1246 AllocGraphicsBuffer(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 = TypeMallocN(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 
 1275 typedef struct {
 1276     int x_min;
 1277     int x_max;
 1278     int y_min;
 1279     int y_max;
 1280 } ClipLimits;
 1281 
 1282 static Boolean
 1283 RefreshClipped(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 
 1382 static Boolean
 1383 FindGraphicHoles(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 */
 1421 static void
 1422 refresh_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 = TypeMallocN(char, (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 
 1638 void
 1639 refresh_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 
 1648 void
 1649 refresh_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 
 1655 void
 1656 scroll_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 
 1680 void
 1681 pixelarea_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 
 1723 void
 1724 chararea_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 
 1745 void
 1746 reset_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
 1761 void
 1762 noleaks_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