Edit

IABSD.fr/xenocara/app/xterm/graphics.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2026-05-14 09:07:11
    Hash : c50f7c66
    Message : Update to xterm 410. Tested by kirill@ and naddy@

  • app/xterm/graphics.c
  • /* $XTermId: graphics.c,v 1.141 2026/04/07 23:17:57 tom Exp $ */
    
    /*
     * Copyright 2013-2025,2026 by Thomas E. Dickey
     * Copyright 2013-2022,2023 by Ross Combs
     *
     *                         All Rights Reserved
     *
     * Permission is hereby granted, free of charge, to any person obtaining a
     * copy of this software and associated documentation files (the
     * "Software"), to deal in the Software without restriction, including
     * without limitation the rights to use, copy, modify, merge, publish,
     * distribute, sublicense, and/or sell copies of the Software, and to
     * permit persons to whom the Software is furnished to do so, subject to
     * the following conditions:
     *
     * The above copyright notice and this permission notice shall be included
     * in all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
     * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     *
     * Except as contained in this notice, the name(s) of the above copyright
     * holders shall not be used in advertising or otherwise to promote the
     * sale, use or other dealings in this Software without prior written
     * authorization.
     */
    
    #include <xterm.h>
    
    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    #include <data.h>
    #include <ptyx.h>
    
    #include <assert.h>
    #include <graphics.h>
    
    #define OPT_INHERIT_COLORS 0
    
    #if OPT_REGIS_GRAPHICS
    #include <graphics_regis.h>
    #endif
    
    #undef DUMP_BITMAP
    #undef DUMP_COLORS
    #undef DEBUG_PALETTE
    #undef DEBUG_PIXEL
    #undef DEBUG_REFRESH
    
    /*
     * graphics TODO list
     *
     * ReGIS:
     * - ship a default alphabet zero font instead of scaling Xft fonts
     * - input cursors
     * - output cursors
     * - mouse/tablet/arrow-key input
     * - fix graphic pages for ReGIS -- they should also apply to text and sixel graphics
     * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
     * - non-ASCII alphabets
     * - enter/leave anywhere in a command
     * - locator key definitions (DECLKD)
     * - command display mode
     * - re-rasterization on window resize
     * - macros
     * - improved fills for narrow angles (track actual lines not just pixels)
     * - hardcopy/screen-capture support (need dialog of some sort for safety)
     * - error reporting
     *
     * sixel:
     * - fix problem where new_row < 0 during sixel parsing (see FIXME)
     * - screen-capture support (need dialog of some sort for safety)
     *
     * VT55/VT105 waveform graphics
     * - everything
     *
     * Tektronix:
     * - color (VT340 4014 emulation, 41xx, IRAF GTERM, and also MS-DOS Kermit color support)
     * - polygon fill (41xx)
     * - clear area extension
     * - area fill extension
     * - pixel operations (RU/RS/RP)
     * - research other 41xx and 42xx extensions
     *
     * common graphics features:
     * - handle light/dark screen modes (CSI?5[hl])
     * - update text fg/bg color which overlaps images
     * - handle graphic updates in scroll regions (verify effect on graphics)
     * - handle rectangular area copies (verify they work with graphics)
     * - invalidate graphics under graphic if same origin, at least as big, and bg not transparent
     * - invalidate graphic if completely scrolled past end of scrollback
     * - invalidate graphic if all pixels are transparent/erased
     * - invalidate graphic if completely scrolled out of alt buffer
     * - posturize requested colors to match hardware palettes (e.g. only four possible shades on VT240)
     * - color register report/restore
     * - ability to select/copy graphics for pasting in other programs
     * - ability to show non-scroll-mode sixel graphics in a separate window
     * - ability to show ReGIS graphics in a separate window
     * - ability to show Tektronix graphics in VT100 window
     * - truncate graphics at bottom edge of terminal?
     * - locator events (DECEFR DECSLE DECELR DECLRP)
     * - locator controller mode (CSI6i / CSI7i)
     *
     * new escape sequences:
     * - way to query text font size without "window ops" (or make "window ops" permissions more fine grained)
     * - way to query and set the number of graphics pages
     *
     * ReGIS extensions:
     * - non-integer text scaling
     * - free distortionless text rotation (vs. simulating the distortion and aligning to 45deg increments)
     * - font characteristics: bold/underline/italic
     * - remove/increase arbitrary limits (pattern size, pages, alphabets, stack size, font names, etc.)
     * - shade/fill with borders
     * - sprites (copy portion of page into/out of buffer with scaling and rotation)
     * - ellipses
     * - 2D patterns
     * - option to set actual graphic size (not just coordinate range)
     * - gradients (for lines and fills)
     * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
     * - transparency
     * - background color as stackable write control
     * - true color (virtual color registers created upon lookup)
     * - anti-aliasing
     * - variable-width (proportional) text
     */
    
    /* font sizes:
     * VT510:
     *   80 Columns 132 Columns Maximum Number of Lines
     *   10 x 16   6 x 16  26 lines + keyboard indicator line
     *   10 x 13   6 x 13  26 lines + keyboard indicator line
     *   10 x 10   6 x 10  42 lines + keyboard indicator line
     *   10 x 8    6 x 8   53 lines + keyboard indicator line
     */
    
    typedef struct allocated_color_register {
        struct allocated_color_register *next;
        Pixel pix;
        short r, g, b;
    } AllocatedColorRegister;
    
    #define LOOKUP_WIDTH 16
    static AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
    
    #define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
    
    static ColorRegister *shared_color_registers;
    static Graphic *displayed_graphics[MAX_GRAPHICS];
    static unsigned next_graphic_id = 0U;
    static unsigned used_graphics;	/* 0 to MAX_GRAPHICS */
    
    static int valid_graphics;
    static GC graphics_gc;
    static XGCValues xgcv;
    static ColorRegister last_color;
    static ColorRegister gc_color;
    
    #define DiffColor(this,that) \
    	(this.r != that.r || \
    	 this.g != that.g || \
    	 this.b != that.b)
    
    static ColorRegister null_color =
    {-1, -1, -1};
    
    static ColorRegister *
    allocRegisters(void)
    {
        return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
    }
    
    static Graphic *
    freeGraphic(Graphic *obj)
    {
        if (obj) {
    	free(obj->pixels);
    	free(obj->private_color_registers);
    	free(obj);
        }
        return NULL;
    }
    
    static Graphic *
    allocGraphic(int max_w, int max_h)
    {
        Graphic *result = TypeCalloc(Graphic);
        if (result) {
    	result->max_width = max_w;
    	result->max_height = max_h;
    	if (!(result->pixels = TypeCallocN(RegisterNum,
    					     (size_t) max_w * (size_t) max_h))) {
    	    result = freeGraphic(result);
    	} else if (!(result->private_color_registers = allocRegisters())) {
    	    result = freeGraphic(result);
    	}
        }
        return result;
    }
    
    #define getActiveSlot(n) \
    	(((n) < MAX_GRAPHICS && \
    	 displayed_graphics[n] && \
    	 displayed_graphics[n]->valid) \
    	 ? displayed_graphics[n] \
    	 : NULL)
    
    static Graphic *
    getInactiveSlot(const TScreen *screen, unsigned n)
    {
        if (n < MAX_GRAPHICS &&
    	(!displayed_graphics[n] ||
    	 !displayed_graphics[n]->valid)) {
    	if (!displayed_graphics[n]) {
    	    displayed_graphics[n] = allocGraphic(screen->graphics_max_wide,
    						 screen->graphics_max_high);
    	    used_graphics += (displayed_graphics[n] != NULL);
    	}
    	return displayed_graphics[n];
        }
        return NULL;
    }
    
    static ColorRegister *
    getSharedRegisters(void)
    {
        if (!shared_color_registers)
    	shared_color_registers = allocRegisters();
        return shared_color_registers;
    }
    
    static void
    deactivateSlot(unsigned n)
    {
        if ((n < MAX_GRAPHICS) && displayed_graphics[n]) {
    	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
    	used_graphics--;
        }
    }
    
    extern RegisterNum
    read_pixel(Graphic *graphic, int x, int y)
    {
        return (((x) >= 0 &&
    	     (x) < (graphic)->actual_width &&
    	     (y) >= 0 &&
    	     (y) < (graphic)->actual_height)
    	    ? (graphic)->pixels[(y) * (graphic)->max_width + (x)]
    	    : (RegisterNum) COLOR_HOLE);
    }
    
    #define _draw_pixel(G, X, Y, C) \
        do { \
            unsigned _cell = (unsigned)((Y) * (G)->max_width + (X)); \
            SetSpixel(G, _cell, (RegisterNum) (C)); \
        } while (0)
    
    void
    draw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
    {
        assert(color <= MAX_COLOR_REGISTERS);
    
    #ifdef DEBUG_PIXEL
        TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
    	   x,
    	   y,
    	   color,
    	   COLOR_HOLE,
    	   ((color != COLOR_HOLE)
    	    ? (unsigned) graphic->color_registers[color].r : 0U),
    	   ((color != COLOR_HOLE)
    	    ? (unsigned) graphic->color_registers[color].g : 0U),
    	   ((color != COLOR_HOLE)
    	    ? (unsigned) graphic->color_registers[color].b : 0U)));
    #endif
        if (x >= 0 && x < graphic->actual_width &&
    	y >= 0 && y < graphic->actual_height) {
    	_draw_pixel(graphic, x, y, color);
    	if (color < MAX_COLOR_REGISTERS)
    	    graphic->color_registers_used[color] = True;
        }
    }
    
    void
    draw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
    {
        int x, y;
        int tmp;
    
        assert(color <= MAX_COLOR_REGISTERS);
    
        if (x1 > x2) {
    	EXCHANGE(x1, x2, tmp);
        }
        if (y1 > y2) {
    	EXCHANGE(y1, y2, tmp);
        }
    
        if (x2 < 0 || x1 >= graphic->actual_width ||
    	y2 < 0 || y1 >= graphic->actual_height)
    	return;
    
        if (x1 < 0)
    	x1 = 0;
        if (x2 >= graphic->actual_width)
    	x2 = graphic->actual_width - 1;
        if (y1 < 0)
    	y1 = 0;
        if (y2 >= graphic->actual_height)
    	y2 = graphic->actual_height - 1;
    
        if (color < MAX_COLOR_REGISTERS)
    	graphic->color_registers_used[color] = True;
        for (y = y1; y <= y2; y++)
    	for (x = x1; x <= x2; x++)
    	    _draw_pixel(graphic, x, y, color);
    }
    
    void
    copy_overlapping_area(Graphic *graphic, int src_ul_x, int src_ul_y,
    		      int dst_ul_x, int dst_ul_y, unsigned w, unsigned h,
    		      unsigned default_color)
    {
        int sx, ex, dx;
        int sy, ey, dy;
        int xx, yy;
        RegisterNum color;
    
        if (dst_ul_x <= src_ul_x) {
    	sx = 0;
    	ex = (int) w - 1;
    	dx = +1;
        } else {
    	sx = (int) w - 1;
    	ex = 0;
    	dx = -1;
        }
    
        if (dst_ul_y <= src_ul_y) {
    	sy = 0;
    	ey = (int) h - 1;
    	dy = +1;
        } else {
    	sy = (int) h - 1;
    	ey = 0;
    	dy = -1;
        }
    
        for (yy = sy; yy != ey + dy; yy += dy) {
    	int dst_y = dst_ul_y + yy;
    	int src_y = src_ul_y + yy;
    	if (dst_y < 0 || dst_y >= (int) graphic->actual_height)
    	    continue;
    
    	for (xx = sx; xx != ex + dx; xx += dx) {
    	    int dst_x = dst_ul_x + xx;
    	    int src_x = src_ul_x + xx;
    	    int cell;
    	    if (dst_x < 0 || dst_x >= (int) graphic->actual_width)
    		continue;
    
    	    if (src_x < 0 || src_x >= (int) graphic->actual_width ||
    		src_y < 0 || src_y >= (int) graphic->actual_height)
    		color = (RegisterNum) default_color;
    	    else
    		color = graphic->pixels[(unsigned) (src_y *
    						    graphic->max_width) +
    					(unsigned) src_x];
    
    	    cell = (int) ((unsigned) (dst_y * graphic->max_width) +
    			  (unsigned) dst_x);
    	    SetSpixel(graphic, cell, color);
    	}
        }
    }
    
    #define set_color_register(color_registers, color, pr, pg, pb) \
    do { \
        assert(color <= MAX_COLOR_REGISTERS); \
        { \
        ColorRegister *reg = &color_registers[color]; \
        reg->r = (short) pr; \
        reg->g = (short) pg; \
        reg->b = (short) pb; \
        } \
    } while (0)
    
    /* Graphics which don't use private colors will act as if they are using a
     * device-wide color palette.
     */
    static void
    set_shared_color_register(unsigned color, int r, int g, int b)
    {
        unsigned ii;
    
        assert(color < MAX_COLOR_REGISTERS);
    
        set_color_register(getSharedRegisters(), color, r, g, b);
    
        if (!used_graphics)
    	return;
    
        FOR_EACH_SLOT(ii) {
    	Graphic *graphic;
    
    	if (!(graphic = getActiveSlot(ii)))
    	    continue;
    	if (graphic->private_colors)
    	    continue;
    
    	if (graphic->color_registers_used[ii]) {
    	    graphic->dirty = True;
    	}
        }
    }
    
    void
    fetch_color_register(Graphic *graphic,
    		     unsigned color,
    		     ColorRegister *reg)
    {
        assert(color < MAX_COLOR_REGISTERS);
    
        reg->r = -1;
        reg->g = -1;
        reg->b = -1;
    
        if (graphic->color_registers_used[color]) {
    	if (graphic->private_colors) {
    	    *reg = graphic->private_color_registers[color];
    	} else {
    	    *reg = getSharedRegisters()[color];
    	}
        }
    }
    
    void
    update_color_register(Graphic *graphic,
    		      unsigned color,
    		      int r,
    		      int g,
    		      int b)
    {
        assert(color < MAX_COLOR_REGISTERS);
    
        if (graphic->private_colors) {
    	set_color_register(graphic->private_color_registers,
    			   color, r, g, b);
    	if (graphic->color_registers_used[color]) {
    	    graphic->dirty = True;
    	}
    	graphic->color_registers_used[color] = True;
        } else {
    	set_shared_color_register(color, r, g, b);
        }
    }
    
    #define SQUARE(X) ( (X) * (X) )
    
    RegisterNum
    find_color_register(ColorRegister const *color_registers, int r, int g, int b)
    {
        unsigned i;
        unsigned closest_index;
        unsigned closest_distance;
    
        /* I have no idea what algorithm DEC used for this.
         * The documentation warns that it is unpredictable, especially with values
         * far away from any allocated color so it is probably a very simple
         * heuristic rather than something fancy like finding the minimum distance
         * in a linear perceptive color space.
         */
        closest_index = MAX_COLOR_REGISTERS;
        closest_distance = 0U;
        for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
    	unsigned d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
    				 SQUARE(3 * (color_registers[i].g - g)) +
    				 SQUARE(1 * (color_registers[i].b - b)));
    	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
    	    closest_index = i;
    	    closest_distance = d;
    	}
        }
    
        TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
    	   r, g, b,
    	   closest_index,
    	   closest_distance,
    	   color_registers[closest_index].r,
    	   color_registers[closest_index].g,
    	   color_registers[closest_index].b));
        return (RegisterNum) closest_index;
    }
    
    #if OPT_INHERIT_COLORS
    static void
    copy_color_registers(Graphic *target, Graphic *source)
    {
        memcpy(target->color_registers_used,
    	   source->color_registers_used,
    	   sizeof(Boolean) * MAX_COLOR_REGISTERS);
    
        memcpy(target->private_color_registers,
    	   source->color_registers,
    	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
    }
    #endif
    
    static void
    init_color_registers(TScreen const *screen, ColorRegister *color_registers)
    {
        const int graphics_termid = GraphicsTermId(screen);
    
        TRACE(("setting initial colors for terminal %d\n", graphics_termid));
        memset(color_registers,
    	   0,
    	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
    
        /*
         * default color registers:
         *     (mono) (color)
         * VK100/GIGI (fixed)
         * VT125:
         *   0: 0%      0%
         *   1: 33%     blue
         *   2: 66%     red
         *   3: 100%    green
         * VT240:
         *   0: 0%      0%
         *   1: 33%     blue
         *   2: 66%     red
         *   3: 100%    green
         * VT241:
         *   0: 0%      0%
         *   1: 33%     blue
         *   2: 66%     red
         *   3: 100%    green
         * VT330:
         *   0: 0%      0%              (bg for light on dark mode)
         *   1: 33%     blue (red?)
         *   2: 66%     red (green?)
         *   3: 100%    green (yellow?) (fg for light on dark mode)
         * VT340:
         *   0: 0%      0%              (bg for light on dark mode)
         *   1: 14%     blue
         *   2: 29%     red
         *   3: 43%     green
         *   4: 57%     magenta
         *   5: 71%     cyan
         *   6: 86%     yellow
         *   7: 100%    50%             (fg for light on dark mode)
         *   8: 0%      25%
         *   9: 14%     gray-blue
         *  10: 29%     gray-red
         *  11: 43%     gray-green
         *  12: 57%     gray-magenta
         *  13: 71%     gray-cyan
         *  14: 86%     gray-yellow
         *  15: 100%    75%             ("white")
         * VT382:
         *   ? (FIXME: B&W only?)
         * dxterm:
         *  ?
         */
        switch (graphics_termid) {
        case 125:
        case 241:
    	set_color_register(color_registers, 0, 0, 0, 0);
    	set_color_register(color_registers, 1, 0, 0, 100);
    	set_color_register(color_registers, 2, 0, 100, 0);
    	set_color_register(color_registers, 3, 100, 0, 0);
    	break;
        case 240:
        case 330:
    	set_color_register(color_registers, 0, 0, 0, 0);
    	set_color_register(color_registers, 1, 33, 33, 33);
    	set_color_register(color_registers, 2, 66, 66, 66);
    	set_color_register(color_registers, 3, 100, 100, 100);
    	break;
        case 340:
        default:
    	set_color_register(color_registers, 0, 0, 0, 0);
    	set_color_register(color_registers, 1, 20, 20, 80);
    	set_color_register(color_registers, 2, 80, 13, 13);
    	set_color_register(color_registers, 3, 20, 80, 20);
    	set_color_register(color_registers, 4, 80, 20, 80);
    	set_color_register(color_registers, 5, 20, 80, 80);
    	set_color_register(color_registers, 6, 80, 80, 20);
    	set_color_register(color_registers, 7, 53, 53, 53);
    	set_color_register(color_registers, 8, 26, 26, 26);
    	set_color_register(color_registers, 9, 33, 33, 60);
    	set_color_register(color_registers, 10, 60, 26, 26);
    	set_color_register(color_registers, 11, 33, 60, 33);
    	set_color_register(color_registers, 12, 60, 33, 60);
    	set_color_register(color_registers, 13, 33, 60, 60);
    	set_color_register(color_registers, 14, 60, 60, 33);
    	set_color_register(color_registers, 15, 80, 80, 80);
    	break;
        case 382:			/* FIXME: verify */
    	set_color_register(color_registers, 0, 0, 0, 0);
    	set_color_register(color_registers, 1, 100, 100, 100);
    	break;
        }
    
    #ifdef DEBUG_PALETTE
        {
    	unsigned i;
    
    	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
    	    TRACE(("initial value for register %03u: %d,%d,%d\n",
    		   i,
    		   color_registers[i].r,
    		   color_registers[i].g,
    		   color_registers[i].b));
    	}
        }
    #endif
    }
    
    unsigned
    get_color_register_count(TScreen const *screen)
    {
        unsigned num_color_registers;
    
        if (screen->numcolorregisters >= 0) {
    	num_color_registers = (unsigned) screen->numcolorregisters;
        } else {
    	num_color_registers = 0U;
        }
    
        if (num_color_registers > 1U) {
    	if (num_color_registers > MAX_COLOR_REGISTERS)
    	    return MAX_COLOR_REGISTERS;
    	return num_color_registers;
        }
    
        /*
         * color capabilities:
         * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
         * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
         * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
         * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
         * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
         * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
         * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
         * dxterm      ?
         */
        switch (screen->graphics_termid) {
        case 125:
    	return 4U;
        case 240:
    	return 4U;
        case 241:
    	return 4U;
        case 330:
    	return 4U;
        case 340:
    	return 16U;
        case 382:
    	return 2U;
        default:
    	/* unknown graphics model -- might as well be generous */
    	return MAX_COLOR_REGISTERS;
        }
    }
    
    static void
    init_graphic(const TScreen *screen,
    	     Graphic *graphic,
    	     unsigned type,
    	     int charrow,
    	     int charcol,
    	     unsigned num_color_registers)
    {
        int private_colors = screen->privatecolorregisters;
        const unsigned max_pixels = (unsigned) (graphic->max_width *
    					    graphic->max_height);
    
        TRACE(("init_graphic %u pixels at %d,%d\n", max_pixels, charrow, charcol));
    
        graphic->hidden = False;
        graphic->dirty = True;
        memset(graphic->pixels, COLOR_HOLE & 0xff, max_pixels * sizeof(RegisterNum));
        memset(graphic->color_registers_used, False, sizeof(graphic->color_registers_used));
    
        /*
         * text and graphics interactions:
         * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
         * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
         * VT382                     text writes on top of graphics buffer FIXME: verify
         * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
         */
    
        /*
         * dimensions (ReGIS logical, physical):
         * VK100/GIGI  768x4??  768x240(status?)
         * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
         * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
         * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
         * VT330       800x480  800x480(+?status)
         * VT340       800x480  800x480(+?status)
         * VT382       960x750  sixel only
         * dxterm      ?x? ?x?  variable?
         */
    
        graphic->actual_width = 0;
        graphic->actual_height = 0;
    
        graphic->pixw = 1;
        graphic->pixh = 1;
    
        graphic->valid_registers = num_color_registers;
        TRACE(("%d color registers\n", graphic->valid_registers));
    
        graphic->private_colors = private_colors;
        if (graphic->private_colors) {
    #if OPT_INHERIT_COLORS
    	unsigned ii;
    	int max_charrow = -1;
    	Graphic *newest = NULL;
    #endif
    
    	TRACE(("using private color registers\n"));
    
    #if OPT_INHERIT_COLORS
    	FOR_EACH_SLOT(ii) {
    	    Graphic *check;
    	    if (!(check = getActiveSlot(ii)))
    		continue;
    	    if (!newest || check->charrow >= max_charrow) {
    		max_charrow = check->charrow;
    		newest = check;
    	    }
    	}
    
    	if (newest != NULL && newest != graphic) {
    	    copy_color_registers(graphic, newest);
    	} else {
    	    init_color_registers(screen, graphic->private_color_registers);
    	}
    #else
    	init_color_registers(screen, graphic->private_color_registers);
    #endif
    	graphic->color_registers = graphic->private_color_registers;
        } else {
    	TRACE(("using shared color registers\n"));
    	graphic->color_registers = getSharedRegisters();
        }
    
        graphic->charrow = charrow;
        graphic->charcol = charcol;
        graphic->type = type;
        graphic->valid = False;
    }
    
    Graphic *
    get_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
    {
        const TScreen *screen = TScreenOf(xw);
        const int bufferid = screen->whichBuf;
        Graphic *graphic = NULL;
        unsigned ii;
    
        TRACE(("get_new_graphic %d,%d type %d\n", charrow, charcol, type));
    
        FOR_EACH_SLOT(ii) {
    	if ((graphic = getInactiveSlot(screen, ii)) != NULL) {
    	    TRACE(("using fresh graphic index %u as id %u\n",
    		   ii, next_graphic_id));
    	    break;
    	}
        }
    
        /* if none are free, recycle the graphic scrolled back the farthest */
        if (!graphic) {
    	int min_charrow = 0;
    	Graphic *min_graphic = NULL;
    	if_TRACE(unsigned best_ii = (1 + MAX_GRAPHICS));
    
    	FOR_EACH_SLOT(ii) {
    	    if (!(graphic = getActiveSlot(ii)))
    		continue;
    	    if (!min_graphic || graphic->charrow < min_charrow) {
    		if_TRACE(best_ii = ii);
    		min_charrow = graphic->charrow;
    		min_graphic = graphic;
    	    }
    	}
    	TRACE(("recycling old graphic index %u as id %u\n",
    	       best_ii, next_graphic_id));
    	graphic = min_graphic;
        }
    
        if (graphic) {
    	unsigned num_color_registers;
    	num_color_registers = get_color_register_count(screen);
    	graphic->xw = xw;
    	graphic->bufferid = bufferid;
    	graphic->id = next_graphic_id++;
    	init_graphic(screen,
    		     graphic,
    		     type,
    		     charrow,
    		     charcol,
    		     num_color_registers);
        }
        return graphic;
    }
    
    Graphic *
    get_new_or_matching_graphic(XtermWidget xw,
    			    int charrow,
    			    int charcol,
    			    int actual_width,
    			    int actual_height,
    			    unsigned type)
    {
        TScreen const *screen = TScreenOf(xw);
        const int bufferid = screen->whichBuf;
        Graphic *graphic;
        unsigned ii;
    
        FOR_EACH_SLOT(ii) {
    	TRACE(("checking slot=%u for graphic at %d,%d %dx%d bufferid=%d type=%u\n", ii,
    	       charrow, charcol,
    	       actual_width, actual_height,
    	       bufferid, type));
    	if ((graphic = getActiveSlot(ii)) != NULL) {
    	    if (graphic->type == type &&
    		graphic->bufferid == bufferid &&
    		graphic->charrow == charrow &&
    		graphic->charcol == charcol &&
    		graphic->actual_width == actual_width &&
    		graphic->actual_height == actual_height) {
    		TRACE(("found existing graphic slot=%u id=%u\n", ii, graphic->id));
    		return graphic;
    	    }
    	    TRACE(("not a match: graphic at %d,%d %dx%d bufferid=%d type=%u\n",
    		   graphic->charrow, graphic->charcol,
    		   graphic->actual_width, graphic->actual_height,
    		   graphic->bufferid, graphic->type));
    	}
        }
    
        /* if no match get a new graphic */
        if ((graphic = get_new_graphic(xw, charrow, charcol, type)) != NULL) {
    	graphic->actual_width = actual_width;
    	graphic->actual_height = actual_height;
    	TRACE(("no match; created graphic at %d,%d %dx%d bufferid=%d type=%u\n",
    	       graphic->charrow, graphic->charcol,
    	       graphic->actual_width, graphic->actual_height,
    	       graphic->bufferid, graphic->type));
        }
        return graphic;
    }
    
    #define ScaleForXColor(s) (unsigned short) ((unsigned long)(s) * MAX_U_COLOR / CHANNEL_MAX)
    
    static int
    save_allocated_color(const ColorRegister *reg, XtermWidget xw, Pixel *pix)
    {
        unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        XColor xcolor;
        AllocatedColorRegister *new_color;
    
        /* *INDENT-EQLS* */
        xcolor.pixel = 0UL;
        xcolor.red   = ScaleForXColor(reg->r);
        xcolor.green = ScaleForXColor(reg->g);
        xcolor.blue  = ScaleForXColor(reg->b);
        xcolor.flags = DoRed | DoGreen | DoBlue;
    
        if (!allocateBestRGB(xw, &xcolor)) {
    	TRACE(("unable to allocate xcolor\n"));
    	*pix = 0UL;
    	return 0;
        } else {
    	*pix = xcolor.pixel;
    
    	if (!(new_color = TypeMalloc(AllocatedColorRegister))) {
    	    TRACE(("unable to save pixel %lu\n", (unsigned long) *pix));
    	    return 0;
    	} else {
    	    new_color->r = reg->r;
    	    new_color->g = reg->g;
    	    new_color->b = reg->b;
    	    new_color->pix = *pix;
    	    new_color->next = allocated_colors[rr][gg][bb];
    
    	    allocated_colors[rr][gg][bb] = new_color;
    
    	    return 1;
    	}
        }
    }
    
    /* FIXME: with so many possible colors we need to determine
     * when to free them to be nice to PseudoColor displays
     */
    static Pixel
    color_register_to_xpixel(const ColorRegister *reg, XtermWidget xw)
    {
        Pixel result;
        unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
        const AllocatedColorRegister *search;
    
        for (search = allocated_colors[rr][gg][bb]; search; search = search->next) {
    	if (search->r == reg->r &&
    	    search->g == reg->g &&
    	    search->b == reg->b) {
    	    return search->pix;
    	}
        }
    
        save_allocated_color(reg, xw, &result);
        return result;
    }
    
    static void
    refresh_graphic(TScreen const *screen,
    		Graphic const *graphic,
    		ColorRegister *buffer,
    		int refresh_x,
    		int refresh_y,
    		int refresh_w,
    		int refresh_h,
    		int draw_x,
    		int draw_y,
    		int draw_w,
    		int draw_h)
    {
        int const pw = graphic->pixw;
        int const ph = graphic->pixh;
        int const graph_x = graphic->charcol * FontWidth(screen);
        int const graph_y = graphic->charrow * FontHeight(screen);
        int const graph_w = graphic->actual_width;
        int const graph_h = graphic->actual_height;
        int const mw = graphic->max_width;
    
        int r, c;
        int pmy;
        RegisterNum regnum;
    
        if_TRACE(int holes = 0);
        if_TRACE(int total = 0);
        if_TRACE(int out_of_range = 0);
    
        TRACE(("refreshing graphic %u from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d)\n",
    	   graphic->id,
    	   graph_x, graph_y, draw_w, draw_h,
    	   graphic->valid,
    	   graphic->actual_width,
    	   graphic->actual_height,
    	   pw, ph,
    	   graphic->max_width,
    	   graphic->max_height));
    
        TRACE(("refresh pixmap starts at %d,%d\n", refresh_x, refresh_y));
    
        for (r = 0, pmy = graph_y; r < graph_h; r++, pmy += ph) {
    	int pmx, buffer_y, pixel_y;
    
    	if (pmy + ph - 1 < draw_y)
    	    continue;
    	if (pmy > draw_y + draw_h - 1)
    	    break;
    
    	if (pmy < draw_y ||
    	    pmy < refresh_y ||
    	    pmy > refresh_y + refresh_h - 1) {
    	    if_TRACE(out_of_range++);
    	    continue;
    	}
    	pixel_y = r * mw;
    	buffer_y = (pmy - refresh_y) * refresh_w;
    
    	for (c = 0, pmx = graph_x; c < graph_w; c++, pmx += pw) {
    
    	    if (pmx + pw - 1 < draw_x)
    		continue;
    	    if (pmx > draw_x + draw_w - 1)
    		break;
    
    	    if (pmx < draw_x ||
    		pmx < refresh_x ||
    		pmx > refresh_x + refresh_w - 1) {
    		if_TRACE(out_of_range++);
    		continue;
    	    }
    
    	    if_TRACE(total++);
    	    regnum = graphic->pixels[pixel_y + c];
    	    if (regnum == COLOR_HOLE) {
    		if_TRACE(holes++);
    	    } else {
    		buffer[buffer_y + (pmx - refresh_x)] =
    		    graphic->color_registers[regnum];
    	    }
    	}
        }
    
        TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes; %d were out of pixmap range\n",
    	   holes, total, out_of_range));
    }
    
    #define MAX_PCT 100.		/* HLS uses this for L, S percentages */
    #define MAX_RGB 100.		/* use this rather than 255 */
    
    /*
     * In HLS, H is an angle, in degrees, and L, S are percentages.
     * Primary color hues:
     *  blue:    0 degrees
     *  red:   120 degrees
     *  green: 240 degrees
     */
    void
    hls2rgb(int h, int l, int s, short *r, short *g, short *b)
    {
        int hs;
        const double lv = l / MAX_PCT;	/* scale to [0..1] */
        const double sv = s / MAX_PCT;	/* scale to [0..1] */
        double c, x, m, c2;
        double r1, g1, b1;
    
        if (s == 0) {		/* the achromatic case */
    	*r = *g = *b = (short) l;
    	return;
        }
    
        h = h - 120;		/* Rotate so that blue is at 0 degrees  */
        while (h < 0)
    	h += 360;		/* Normalize to [0..360) */
        while (h >= 360)
    	h -= 360;
    
        hs = (h % 120) - 60;	/* compute abs((h % 120) - 60) */
        if (hs < 0)
    	hs = -hs;
    
        c2 = (2.0 * lv) - 1.0;	/* compute abs((2 * lv) - 1) */
        if (c2 < 0.0)
    	c2 = -c2;
    
        c = (1.0 - c2) * sv;
        x = ((60 - hs) / 60.) * c;
        m = lv - 0.5 * c;
    
        switch (h / 60) {		/* result in six intervals */
        case 0:
    	r1 = c;
    	g1 = x;
    	b1 = 0.0;
    	break;
        case 1:
    	r1 = x;
    	g1 = c;
    	b1 = 0.0;
    	break;
        case 2:
    	r1 = 0.0;
    	g1 = c;
    	b1 = x;
    	break;
        case 3:
    	r1 = 0.0;
    	g1 = x;
    	b1 = c;
    	break;
        case 4:
    	r1 = x;
    	g1 = 0.0;
    	b1 = c;
    	break;
        case 5:
    	r1 = c;
    	g1 = 0.0;
    	b1 = x;
    	break;
        default:
    	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
    	*r = (short) 360;
    	*g = (short) MAX_PCT;
    	*b = (short) MAX_PCT;
    	return;
        }
    
        *r = (short) ((r1 + m) * MAX_PCT + 0.5);
        *g = (short) ((g1 + m) * MAX_PCT + 0.5);
        *b = (short) ((b1 + m) * MAX_PCT + 0.5);
    
        if (*r < 0)
    	*r = 0;
        else if (*r > 100)
    	*r = 100;
        if (*g < 0)
    	*g = 0;
        else if (*g > 100)
    	*g = 100;
        if (*b < 0)
    	*b = 0;
        else if (*b > 100)
    	*b = 100;
    }
    
    void
    rgb2hls(int r, int g, int b, short *h, short *l, short *s)
    {
        const double scaled_r = (r / MAX_RGB);
        const double scaled_g = (g / MAX_RGB);
        const double scaled_b = (b / MAX_RGB);
    
        const double min_scale = Min(Min(scaled_r, scaled_g), scaled_b);
        const double max_scale = Max(Max(scaled_r, scaled_g), scaled_b);
        const double dif_scale = max_scale - min_scale;
    
        double h_work = 0.;
        double s_work = 0.;
        double l_work = ((max_scale + min_scale) / 2.);
    
        if (dif_scale != 0.) {
    	if (l_work < 0.5f) {
    	    s_work = (dif_scale / (max_scale + min_scale));
    	} else {
    	    s_work = (dif_scale / (2. - max_scale - min_scale));
    	}
    
    	if (scaled_r == max_scale) {
    	    h_work = (scaled_g - scaled_b) / dif_scale;
    	} else if (scaled_g == max_scale) {
    	    h_work = 2. + (scaled_b - scaled_r) / dif_scale;
    	} else if (scaled_b == max_scale) {
    	    h_work = 4. + (scaled_r - scaled_g) / dif_scale;
    	}
        }
    
        h_work *= 60.;
        if (h_work < 0)
    	h_work += 360.;
    
        s_work *= MAX_RGB;
        l_work *= MAX_RGB;
    
        *h = (short) h_work;
        *s = (short) s_work;
        *l = (short) l_work;
    }
    
    void
    dump_graphic(Graphic const *graphic)
    {
    #if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
        RegisterNum color;
    #endif
    #ifdef DUMP_BITMAP
        int r, c;
        ColorRegister const *reg;
    #endif
    
        (void) graphic;
    
        TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
    	   graphic->id,
    	   graphic->charrow,
    	   graphic->charcol,
    	   graphic->actual_width,
    	   graphic->actual_height,
    	   graphic->pixw,
    	   graphic->pixh));
    
    #ifdef DUMP_COLORS
        TRACE(("graphic colors:\n"));
        for (color = 0; color < graphic->valid_registers; color++) {
    	TRACE(("%03u: %d,%d,%d\n",
    	       color,
    	       graphic->color_registers[color].r,
    	       graphic->color_registers[color].g,
    	       graphic->color_registers[color].b));
        }
    #endif
    
    #ifdef DUMP_BITMAP
        TRACE(("graphic pixels:\n"));
        for (r = 0; r < graphic->actual_height; r++) {
    	for (c = 0; c < graphic->actual_width; c++) {
    	    color = graphic->pixels[r * graphic->max_width + c];
    	    if (color == COLOR_HOLE) {
    		TRACE(("?"));
    	    } else {
    		reg = &graphic->color_registers[color];
    		if (reg->r + reg->g + reg->b > 200) {
    		    TRACE(("#"));
    		} else if (reg->r + reg->g + reg->b > 150) {
    		    TRACE(("%%"));
    		} else if (reg->r + reg->g + reg->b > 100) {
    		    TRACE((":"));
    		} else if (reg->r + reg->g + reg->b > 80) {
    		    TRACE(("."));
    		} else {
    		    TRACE((" "));
    		}
    	    }
    	}
    	TRACE(("\n"));
        }
    
        TRACE(("\n"));
    #endif
    }
    
    /* Erase the portion of any displayed graphic overlapping with a rectangle
     * of the given size and location in pixels relative to the start of the
     * graphic.  This is used to allow text to "erase" graphics underneath it.
     */
    static void
    erase_graphic(Graphic *graphic, int x, int y, int w, int h)
    {
        const int pw = graphic->pixw;
        const int ph = graphic->pixh;
        const int r_min = y - ph + 1;
        const int r_max = y + h - 1;
        const int c_min = x - pw + 1;
        const int c_max = x + w - 1;
    
        int r;
        int rbase = 0;
    
        TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
    
        for (r = 0; r < graphic->actual_height; r++) {
    	if (rbase >= r_min
    	    && rbase <= r_max) {
    	    int c;
    	    int cbase = 0;
    	    for (c = 0; c < graphic->actual_width; c++) {
    		if (cbase >= c_min
    		    && cbase <= c_max) {
    		    const int cell = r * graphic->max_width + c;
    		    ClrSpixel(graphic, cell);
    		}
    		cbase += pw;
    	    }
    	}
    	rbase += ph;
        }
    }
    
    static int
    compare_graphic_ids(const void *left, const void *right)
    {
        const Graphic *l = *(const Graphic *const *) left;
        const Graphic *r = *(const Graphic *const *) right;
    
        if (!l->valid || !r->valid)
    	return 0;
    
        if (l->bufferid < r->bufferid)
    	return -1;
        else if (l->bufferid > r->bufferid)
    	return 1;
    
        if (l->id < r->id)
    	return -1;
        else
    	return 1;
    }
    
    static void
    clip_area(int *orig_x, int *orig_y, int *orig_w, int *orig_h,
    	  int clip_x, int clip_y, int clip_w, int clip_h)
    {
        if (*orig_x < clip_x) {
    	const int diff = clip_x - *orig_x;
    	*orig_x += diff;
    	*orig_w -= diff;
        }
        if (*orig_w > 0 && *orig_x + *orig_w > clip_x + clip_w) {
    	*orig_w -= (*orig_x + *orig_w) - (clip_x + clip_w);
        }
    
        if (*orig_y < clip_y) {
    	const int diff = clip_y - *orig_y;
    	*orig_y += diff;
    	*orig_h -= diff;
        }
        if (*orig_h > 0 && *orig_y + *orig_h > clip_y + clip_h) {
    	*orig_h -= (*orig_y + *orig_h) - (clip_y + clip_h);
        }
    }
    
    static Bool
    GetGraphicsOrder(TScreen *screen,
    		 int skip_clean,
    		 Graphic *ordered_graphics[MAX_GRAPHICS],
    		 unsigned *resultp)
    {
        unsigned ii;
        unsigned active_count;
    
        *resultp = active_count = 0;
        FOR_EACH_SLOT(ii) {
    	Graphic *graphic;
    	if (!(graphic = getActiveSlot(ii)))
    	    continue;
    	TRACE(("refreshing graphic %d on buffer %d, current buffer %d\n",
    	       graphic->id, graphic->bufferid, screen->whichBuf));
    	if (screen->whichBuf == 0) {
    	    if (graphic->bufferid != 0) {
    		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d\n",
    		       graphic->id, graphic->bufferid, screen->whichBuf));
    		continue;
    	    }
    	} else {
    	    if (graphic->bufferid == 0 && graphic->charrow >= 0) {
    		TRACE(("skipping graphic %d from normal buffer (%d) when drawing screen=%d because it is not in scrollback area\n",
    		       graphic->id, graphic->bufferid, screen->whichBuf));
    		continue;
    	    }
    	    if (graphic->bufferid == 1 &&
    		graphic->charrow + (graphic->actual_height +
    				    FontHeight(screen) - 1) /
    		FontHeight(screen) < 0) {
    		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d because it is completely in scrollback area\n",
    		       graphic->id, graphic->bufferid, screen->whichBuf));
    		continue;
    	    }
    	}
    	if (graphic->hidden)
    	    continue;
    	ordered_graphics[active_count++] = graphic;
        }
    
        if (active_count == 0)
    	return False;
        if (active_count > 1) {
    	qsort(ordered_graphics,
    	      (size_t) active_count,
    	      sizeof(ordered_graphics[0]),
    	      compare_graphic_ids);
        }
    
        if (skip_clean) {
    	unsigned jj;
    	unsigned skip_count;
    
    	for (jj = 0; jj < active_count; ++jj) {
    	    if (ordered_graphics[jj]->dirty)
    		break;
    	}
    	skip_count = jj;
    	if (skip_count == active_count)
    	    return False;
    
    	active_count -= skip_count;
    	for (jj = 0; jj < active_count; ++jj) {
    	    ordered_graphics[jj] = ordered_graphics[jj + skip_count];
    	}
        }
        *resultp = active_count;
        return True;
    }
    
    static ColorRegister *
    AllocGraphicsBuffer(TScreen *screen,
    		    int ncols,
    		    int nrows)
    {
        int const refresh_w = ncols * FontWidth(screen);
        int const refresh_h = nrows * FontHeight(screen);
        ColorRegister *buffer;
    
        if (!(buffer = TypeMallocN(ColorRegister,
    			         (unsigned) refresh_w * (unsigned) refresh_h))) {
    	TRACE(("unable to allocate %dx%d buffer for graphics refresh\n",
    	       refresh_w, refresh_h));
        } else {
    	/* assuming two's complement, the memset will be much faster than loop */
    	if ((unsigned short) null_color.r == 0xffff) {
    	    memset(buffer, 0xff,
    		   sizeof(ColorRegister) * (size_t) (refresh_h * refresh_w));
    	} else {
    	    int yy;
    
    	    for (yy = 0; yy < refresh_h; yy++) {
    		int xx;
    
    		for (xx = 0; xx < refresh_w; xx++) {
    		    buffer[yy * refresh_w + xx] = null_color;
    		}
    	    }
    	}
        }
        return buffer;
    }
    
    typedef struct {
        int x_min;
        int x_max;
        int y_min;
        int y_max;
    } ClipLimits;
    
    static Boolean
    RefreshClipped(TScreen *screen,
    	       int leftcol,
    	       int toprow,
    	       int ncols,
    	       int nrows,
    	       Graphic *ordered_graphics[MAX_GRAPHICS],
    	       unsigned active_count,
    	       ColorRegister *buffer,
    	       ClipLimits * result)
    {
        int const scroll_y = screen->topline * FontHeight(screen);
        int const refresh_x = leftcol * FontWidth(screen);
        int const refresh_y = toprow * FontHeight(screen) + scroll_y;
        int const refresh_w = ncols * FontWidth(screen);
        int const refresh_h = nrows * FontHeight(screen);
        ClipLimits my_limits;
        unsigned jj;
    
        int const altarea_x = 0;
        int const altarea_y = 0;
        int const altarea_w = Width(screen) * FontWidth(screen);
        int const altarea_h = Height(screen) * FontHeight(screen);
    
        int const scrollarea_x = 0;
        int const scrollarea_y = scroll_y;
        int const scrollarea_w = Width(screen) * FontWidth(screen);
        int const scrollarea_h = -scroll_y;
    
        int const mainarea_x = 0;
        int const mainarea_y = scroll_y;
        int const mainarea_w = Width(screen) * FontWidth(screen);
        int const mainarea_h = -scroll_y + Height(screen) * FontHeight(screen);
    
        my_limits.x_min = refresh_x + refresh_w;
        my_limits.x_max = refresh_x - 1;
        my_limits.y_min = refresh_y + refresh_h;
        my_limits.y_max = refresh_y - 1;
        for (jj = 0; jj < active_count; ++jj) {
    	Graphic *graphic = ordered_graphics[jj];
    	int draw_x = graphic->charcol * FontWidth(screen);
    	int draw_y = graphic->charrow * FontHeight(screen);
    	int draw_w = graphic->actual_width;
    	int draw_h = graphic->actual_height;
    
    	if (screen->whichBuf != 0) {
    	    if (graphic->bufferid != 0) {
    		/* clip to alt buffer */
    		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
    			  altarea_x, altarea_y, altarea_w, altarea_h);
    	    } else {
    		/* clip to scrollback area */
    		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
    			  scrollarea_x, scrollarea_y,
    			  scrollarea_w, scrollarea_h);
    	    }
    	} else {
    	    /* clip to scrollback + normal area */
    	    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
    		      mainarea_x, mainarea_y,
    		      mainarea_w, mainarea_h);
    	}
    
    	clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
    		  refresh_x, refresh_y, refresh_w, refresh_h);
    
    	TRACE(("refresh: graph=%u\n", jj));
    	TRACE(("         refresh_x=%d refresh_y=%d refresh_w=%d refresh_h=%d\n",
    	       refresh_x, refresh_y, refresh_w, refresh_h));
    	TRACE(("         draw_x=%d draw_y=%d draw_w=%d draw_h=%d\n",
    	       draw_x, draw_y, draw_w, draw_h));
    
    	if (draw_w > 0 && draw_h > 0) {
    	    refresh_graphic(screen, graphic, buffer,
    			    refresh_x, refresh_y,
    			    refresh_w, refresh_h,
    			    draw_x, draw_y,
    			    draw_w, draw_h);
    	    if (draw_x < my_limits.x_min)
    		my_limits.x_min = draw_x;
    	    if (draw_x + draw_w - 1 > my_limits.x_max)
    		my_limits.x_max = draw_x + draw_w - 1;
    	    if (draw_y < my_limits.y_min)
    		my_limits.y_min = draw_y;
    	    if (draw_y + draw_h - 1 > my_limits.y_max)
    		my_limits.y_max = draw_y + draw_h - 1;
    	}
    	graphic->dirty = False;
        }
    
        if (my_limits.x_max < refresh_x ||
    	my_limits.x_min > refresh_x + refresh_w - 1 ||
    	my_limits.y_max < refresh_y ||
    	my_limits.y_min > refresh_y + refresh_h - 1) {
    	return False;
        }
        *result = my_limits;
        return True;
    }
    
    static Boolean
    FindGraphicHoles(int refresh_x,
    		 int refresh_y,
    		 int refresh_w,
    		 ColorRegister *buffer,
    		 const ClipLimits * limits,
    		 unsigned *result)
    {
        const int y_min = limits->y_min - refresh_y;
        const int y_max = limits->y_max - refresh_y;
        const int x_min = limits->x_min - refresh_x;
        const int x_max = limits->x_max - refresh_x;
        const ColorRegister *base = buffer + (y_min * refresh_w);
        int xx, yy;
    
        unsigned holes = 0U;
        unsigned non_holes = 0U;
    
        for (yy = y_min; yy <= y_max; yy++) {
    	const ColorRegister *scan = base + x_min;
    	for (xx = x_min; xx <= x_max; xx++) {
    	    if (scan->r < 0 || scan->g < 0 || scan->b < 0) {
    		holes++;
    	    } else {
    		non_holes++;
    	    }
    	    if (non_holes && holes)
    		goto finish;
    	    ++scan;
    	}
    	base += refresh_w;
        }
    
      finish:
        *result = holes;
        return (non_holes != 0);
    }
    
    /* the coordinates are relative to the screen */
    static void
    refresh_graphics(XtermWidget xw,
    		 int leftcol,
    		 int toprow,
    		 int ncols,
    		 int nrows,
    		 int skip_clean)
    {
        TScreen *const screen = TScreenOf(xw);
        Display *const display = screen->display;
        Window const drawable = VDrawable(screen);
        int const scroll_y = screen->topline * FontHeight(screen);
        int const refresh_x = leftcol * FontWidth(screen);
        int const refresh_y = toprow * FontHeight(screen) + scroll_y;
        int const refresh_w = ncols * FontWidth(screen);
    
        Graphic *ordered_graphics[MAX_GRAPHICS];
        unsigned active_count;
        unsigned holes;
        int xx, yy;
    
        ColorRegister *buffer;
        ClipLimits clip_limits;
    
        if_TRACE(int const refresh_h = nrows * FontHeight(screen));
    
        if (!GetGraphicsOrder(screen, skip_clean, ordered_graphics, &active_count))
    	return;
    
        if (!valid_graphics) {
    	memset(&xgcv, 0, sizeof(xgcv));
    	xgcv.graphics_exposures = False;
    	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
    	last_color = null_color;
    	gc_color = null_color;
    	if (graphics_gc == NULL) {
    	    TRACE(("unable to allocate GC for graphics refresh\n"));
    	    valid_graphics = -1;
    	} else {
    	    valid_graphics = 1;
    	}
        }
        if (valid_graphics < 0)
    	return;
    
        if ((buffer = AllocGraphicsBuffer(screen, ncols, nrows)) == NULL)
    	return;
    
        TRACE(("refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d (%d,%d %dx%d)\n",
    	   screen->topline,
    	   leftcol, toprow,
    	   nrows, ncols,
    	   refresh_x, refresh_y,
    	   refresh_w, refresh_h));
    
        if (!RefreshClipped(screen, leftcol, toprow, ncols, nrows,
    			ordered_graphics,
    			active_count,
    			buffer,
    			&clip_limits)) {
    	free(buffer);
    	return;
        }
    
        if (!FindGraphicHoles(refresh_x,
    			  refresh_y,
    			  refresh_w,
    			  buffer,
    			  &clip_limits,
    			  &holes)) {
    	TRACE(("refresh: visible graphics areas are erased; nothing to do\n"));
    	free(buffer);
    	return;
        }
    
        /*
         * If we have any holes we can't just copy an image rectangle, and masking
         * with bitmaps is very expensive.  This fallback is surprisingly faster
         * than the XPutImage version in some cases, but I don't know why.
         * (This is even though there's no X11 primitive for drawing a horizontal
         * line of height one and no attempt is made to handle multiple lines at
         * once.)
         */
        if (holes > 0U) {
    	int run;
    
    	run = 0;
    	for (yy = clip_limits.y_min - refresh_y;
    	     yy <= clip_limits.y_max - refresh_y;
    	     yy++) {
    	    for (xx = clip_limits.x_min - refresh_x;
    		 xx <= clip_limits.x_max - refresh_x;
    		 xx++) {
    		const ColorRegister color = buffer[yy * refresh_w + xx];
    
    		if (color.r < 0 || color.g < 0 || color.b < 0) {
    		    last_color = color;
    		    if (run > 0) {
    			XDrawLine(display, drawable, graphics_gc,
    				  OriginX(screen) + refresh_x + xx - run,
    				  (OriginY(screen) - scroll_y) + refresh_y + yy,
    				  OriginX(screen) + refresh_x + xx - 1,
    				  (OriginY(screen) - scroll_y) + refresh_y + yy);
    			run = 0;
    		    }
    		    continue;
    		}
    
    		if (DiffColor(color, last_color)) {
    		    last_color = color;
    		    if (run > 0) {
    			XDrawLine(display, drawable, graphics_gc,
    				  OriginX(screen) + refresh_x + xx - run,
    				  (OriginY(screen) - scroll_y) + refresh_y + yy,
    				  OriginX(screen) + refresh_x + xx - 1,
    				  (OriginY(screen) - scroll_y) + refresh_y + yy);
    			run = 0;
    		    }
    
    		    if (DiffColor(color, gc_color)) {
    			xgcv.foreground =
    			    color_register_to_xpixel(&color, xw);
    			XChangeGC(display, graphics_gc, GCForeground, &xgcv);
    			gc_color = color;
    		    }
    		}
    		run++;
    	    }
    	    if (run > 0) {
    		last_color = null_color;
    		XDrawLine(display, drawable, graphics_gc,
    			  OriginX(screen) + refresh_x + xx - run,
    			  (OriginY(screen) - scroll_y) + refresh_y + yy,
    			  OriginX(screen) + refresh_x + xx - 1,
    			  (OriginY(screen) - scroll_y) + refresh_y + yy);
    		run = 0;
    	    }
    	}
        } else {
    	ColorRegister old_colors[2];
    	Pixel fg, old_result[2];
    	XImage *image;
    	char *imgdata;
    	const unsigned image_w = ((unsigned) clip_limits.x_max + 1U -
    				  (unsigned) clip_limits.x_min);
    	const unsigned image_h = ((unsigned) clip_limits.y_max + 1U -
    				  (unsigned) clip_limits.y_min);
    	int nn;
    
    	image = XCreateImage(display, xw->visInfo->visual,
    			     (unsigned) xw->visInfo->depth,
    			     ZPixmap, 0, NULL,
    			     image_w, image_h,
    			     (int) (sizeof(int) * 8U), 0);
    	if (!image) {
    	    TRACE(("unable to allocate XImage for graphics refresh\n"));
    	    free(buffer);
    	    return;
    	}
    	imgdata = TypeMallocN(char, (size_t)(image_h * (unsigned)image->bytes_per_line));
    	if (!imgdata) {
    	    TRACE(("unable to allocate XImage for graphics refresh\n"));
    	    XDestroyImage(image);
    	    free(buffer);
    	    return;
    	}
    	image->data = imgdata;
    
    	fg = 0U;
    	nn = 0;
    
    	/* two-level cache cuts down on lookup-calls */
    	old_result[0] = 0U;
    	old_result[1] = 0U;
    	old_colors[0] = null_color;
    	old_colors[1] = null_color;
    
    	for (yy = clip_limits.y_min - refresh_y;
    	     yy <= clip_limits.y_max - refresh_y;
    	     yy++) {
    	    for (xx = clip_limits.x_min - refresh_x;
    		 xx <= clip_limits.x_max - refresh_x;
    		 xx++) {
    		const ColorRegister color = buffer[yy * refresh_w + xx];
    
    		if (DiffColor(color, old_colors[nn])) {
    		    if (DiffColor(color, old_colors[!nn])) {
    			nn = !nn;
    			fg = color_register_to_xpixel(&color, xw);
    			old_result[nn] = fg;
    			old_colors[nn] = color;
    		    } else {
    			nn = !nn;
    			fg = old_result[nn];
    		    }
    		}
    
    		XPutPixel(image,
    			  xx + refresh_x - clip_limits.x_min,
    			  yy + refresh_y - clip_limits.y_min, fg);
    	    }
    	}
    
    	XPutImage(display, drawable, graphics_gc, image,
    		  0, 0,
    		  OriginX(screen) + clip_limits.x_min,
    		  (OriginY(screen) - scroll_y) + clip_limits.y_min,
    		  image_w, image_h);
    	free(imgdata);
    	image->data = NULL;
    	XDestroyImage(image);
        }
    
        free(buffer);
        XFlush(display);
    }
    
    void
    refresh_displayed_graphics(XtermWidget xw,
    			   int leftcol,
    			   int toprow,
    			   int ncols,
    			   int nrows)
    {
        refresh_graphics(xw, leftcol, toprow, ncols, nrows, 0);
    }
    
    void
    refresh_modified_displayed_graphics(XtermWidget xw)
    {
        TScreen const *screen = TScreenOf(xw);
        refresh_graphics(xw, 0, 0, MaxCols(screen), MaxRows(screen), 1);
    }
    
    void
    scroll_displayed_graphics(XtermWidget xw, int rows)
    {
        if (used_graphics) {
    	TScreen const *screen = TScreenOf(xw);
    	unsigned ii;
    
    	TRACE(("graphics scroll: moving all up %d rows\n", rows));
    	/* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
    
    	FOR_EACH_SLOT(ii) {
    	    Graphic *graphic;
    
    	    if (!(graphic = getActiveSlot(ii)))
    		continue;
    	    if (graphic->bufferid != screen->whichBuf)
    		continue;
    	    if (graphic->hidden)
    		continue;
    
    	    graphic->charrow -= rows;
    	}
        }
    }
    
    static void
    pixelarea_clear_displayed_graphics(TScreen const *screen,
    				   int winx,
    				   int winy,
    				   int w,
    				   int h)
    {
        unsigned ii;
    
        if (!used_graphics)
    	return;
    
        FOR_EACH_SLOT(ii) {
    	Graphic *graphic;
    	/* FIXME: are these coordinates (scrolled) screen-relative? */
    	int const scroll_y = (screen->whichBuf == 0
    			      ? screen->topline * FontHeight(screen)
    			      : 0);
    	int graph_x;
    	int graph_y;
    	int x, y;
    
    	if (!(graphic = getActiveSlot(ii)))
    	    continue;
    	if (graphic->bufferid != screen->whichBuf)
    	    continue;
    	if (graphic->hidden)
    	    continue;
    
    	graph_x = graphic->charcol * FontWidth(screen);
    	graph_y = graphic->charrow * FontHeight(screen);
    	x = winx - graph_x;
    	y = (winy - scroll_y) - graph_y;
    
    	TRACE(("pixelarea clear graphics: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
    	       screen->topline,
    	       winx, winy,
    	       w, h,
    	       x, y));
    	erase_graphic(graphic, x, y, w, h);
        }
    }
    
    void
    chararea_clear_displayed_graphics(TScreen const *screen,
    				  int leftcol,
    				  int toprow,
    				  int ncols,
    				  int nrows)
    {
        if (used_graphics) {
    	int const x = leftcol * FontWidth(screen);
    	int const y = toprow * FontHeight(screen);
    	int const w = ncols * FontWidth(screen);
    	int const h = nrows * FontHeight(screen);
    
    	TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
    	       screen->topline,
    	       leftcol, toprow,
    	       nrows, ncols,
    	       x, y, w, h));
    	pixelarea_clear_displayed_graphics(screen, x, y, w, h);
        }
    }
    
    void
    reset_displayed_graphics(TScreen const *screen)
    {
        init_color_registers(screen, getSharedRegisters());
    
        if (used_graphics) {
    	unsigned ii;
    
    	TRACE(("resetting all graphics\n"));
    	FOR_EACH_SLOT(ii) {
    	    deactivateSlot(ii);
    	}
    #if OPT_REGIS_GRAPHICS
    	reset_regis();
    #endif
        }
    }
    
    #ifdef NO_LEAKS
    void
    noleaks_graphics(Display *dpy)
    {
        unsigned ii;
    
        FOR_EACH_SLOT(ii) {
    	deactivateSlot(ii);
        }
        if (valid_graphics > 0)
    	XFreeGC(dpy, graphics_gc);
    }
    #endif