/* $XTermId: graphics.c,v 1.12 2013/07/10 22:35:28 Ross.Combs Exp $ */

/*
 * Copyright 2013 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 <math.h>
#include <ctype.h>
#include <stdlib.h>

#include <data.h>
#include <VTparse.h>
#include <ptyx.h>

#include <assert.h>
#include <graphics.h>

#undef DUMP_SIXEL_BITMAP
#undef DEBUG_REFRESH

/* TODO:
 * ReGIS:
 * - everything
 * sixel:
 * - erase graphic when erasing screen
 * - maintain ordered list/array instead of qsort()
 * - erase text under graphics if bg not transparent
 * - erase scrolled portions of all graphics on alt buffer
 * - delete graphic if scrolled past end of scrollback
 * - delete graphic if all pixels are transparent/erased
 * - dynamic memory allocation, add configurable limits
 * - auto convert color graphics in VT330 mode
 * - investigate second graphic framebuffer for ReGIS -- does this apply to text and sixel graphics?
 * - fix problem where new_row < 0 during sixel parsing (see FIXME)
 * VT55/VT105 waveform graphics
 * - everything
 * escape sequences
 * - way to query font size without "window ops" (or make "window ops" permissions more fine grained)
 * - way to query and/or set the maximum number of color registers
 */

/* font sizes:
 * VT510:
 *   80 Columns 132 Columns Maximum Number of Lines
 *   10 x 16   6 x 16  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
 *   10 x 13   6 x 13  26 lines + keyboard indicator line
*/

/***====================================================================***/
/*
 * Parse numeric parameters which have the operator as a prefix rather than a
 * suffix as in ANSI format.
 *
 *  #             0
 *  #1            1
 *  #1;           1
 *  "1;2;640;480  4
 *  #1;2;0;0;0    5
 */
static void
parse_prefixedtype_params(ANSI *params, const char **string)
{
    const char *cp = *string;
    ParmType nparam = 0;
    int last_empty = 1;

    memset(params, 0, sizeof(*params));
    params->a_final = CharOf(*cp);
    if (*cp != '\0')
	cp++;

    while (*cp != '\0') {
	Char ch = CharOf(*cp);

	if (isdigit(ch)) {
	    last_empty = 0;
	    if (nparam < NPARAM) {
		params->a_param[nparam] =
		    (ParmType) ((params->a_param[nparam] * 10)
				+ (ch - '0'));
	    }
	} else if (ch == ';') {
	    last_empty = 1;
	    nparam++;
	} else if (ch == ' ' || ch == '\r' || ch == '\n') {
	    /* EMPTY */ ;
	} else {
	    break;
	}
	cp++;
    }

    *string = cp;
    if (!last_empty)
	nparam++;
    if (nparam > NPARAM)
	params->a_nparam = NPARAM;
    else
	params->a_nparam = nparam;
}

typedef struct {
    Pixel pix;
    short r, g, b;
    short allocated;
} ColorRegister;

#define MAX_COLOR_REGISTERS 256U
#define COLOR_HOLE ((unsigned short)MAX_COLOR_REGISTERS)
#define BUFFER_WIDTH 1000
#define BUFFER_HEIGHT 800
typedef struct {
    RegisterNum pixels[BUFFER_HEIGHT * BUFFER_WIDTH];
    ColorRegister private_color_registers[MAX_COLOR_REGISTERS];
    ColorRegister *color_registers;
    char color_registers_used[MAX_COLOR_REGISTERS];
    XtermWidget xw;
    int max_width;		/* largest image which can be stored */
    int max_height;		/* largest image which can be stored */
    RegisterNum current_register;
    int valid_registers;	/* for wrap-around behavior */
    int device_background;	/* 0: set to color 0, 1: unchanged */
    int background;		/* current background color */
    int aspect_vertical;
    int aspect_horizontal;
    int declared_width;		/* size as reported by the application */
    int declared_height;	/* size as reported by the application */
    int actual_width;		/* size measured during parsing */
    int actual_height;		/* size measured during parsing */
    int private_colors;		/* if not using the shared color registers */
    int charrow;		/* upper left starting point in characters */
    int charcol;		/* upper left starting point in characters */
    int pixw;			/* width of graphic pixels in screen pixels */
    int pixh;			/* height of graphic pixels in screen pixels */
    int row;			/* context used during parsing */
    int col;			/* context used during parsing */
    int bufferid;		/* which screen buffer the graphic is associated with */
    unsigned int id;		/* sequential id used for preserving layering */
    int valid;			/* if the graphic has been initialized */
    int dirty;			/* if the graphic needs to be redrawn */
} SixelGraphic;

static unsigned int next_sixel_id = 0U;

static ColorRegister shared_color_registers[MAX_COLOR_REGISTERS];

#define MAX_SIXEL_GRAPHICS 16U
static SixelGraphic sixel_graphics[MAX_SIXEL_GRAPHICS];

/* sixel scrolling:
 * VK100/GIGI ? (did it even support Sixel?)
 * VT125      unsupported
 * VT240      unsupported
 * VT241      unsupported
 * VT330      mode setting
 * VT340      mode setting
 * dxterm     ?
 */

static void
init_sixel_background(SixelGraphic *graphic)
{
    RegisterNum bgcolor = (RegisterNum) graphic->background;
    int r, c;

    TRACE(("initializing sixel background to size=%dx%d bgcolor=%hu\n",
	   graphic->declared_width,
	   graphic->declared_height,
	   bgcolor));
    for (r = 0; r < graphic->max_height; r++) {
	for (c = 0; c < graphic->max_width; c++) {
	    if (c < graphic->declared_width && r < graphic->declared_height) {
		graphic->pixels[r * graphic->max_width + c] = bgcolor;
	    } else {
		graphic->pixels[r * graphic->max_width + c] = COLOR_HOLE;
	    }
	}
    }
}

static void
set_sixel(SixelGraphic *graphic, int sixel)
{
    RegisterNum color;
    int pix;

    color = graphic->current_register;
    TRACE(("drawing sixel at pos=%d,%d color=%hu (hole=%d, [%d,%d,%d])\n",
	   graphic->col,
	   graphic->row,
	   color,
	   color == COLOR_HOLE,
	   ((color != COLOR_HOLE)
	    ? (unsigned int) graphic->color_registers[color].r : 0U),
	   ((color != COLOR_HOLE)
	    ? (unsigned int) graphic->color_registers[color].g : 0U),
	   ((color != COLOR_HOLE)
	    ? (unsigned int) graphic->color_registers[color].b : 0U)));
    for (pix = 0; pix < 6; pix++) {
	if (graphic->col < graphic->max_width &&
	    graphic->row + pix < graphic->max_height) {
	    if (sixel & (1 << pix)) {
		if (graphic->col + 1 > graphic->actual_width) {
		    graphic->actual_width = graphic->col + 1;
		}
		if (graphic->row + pix + 1 > graphic->actual_height) {
		    graphic->actual_height = graphic->row + pix + 1;
		}
		graphic->pixels[
				   (((graphic->row + pix) * graphic->max_width)
				    + graphic->col)
		    ] = color;
	    }
	} else {
	    TRACE(("sixel pixel %d out of bounds\n", pix));
	}
    }
}

static void
set_sixel_color_register(ColorRegister *color_registers,
			 RegisterNum color,
			 short r,
			 short g,
			 short b)
{
    ColorRegister *reg = &color_registers[color];
    reg->r = r;
    reg->g = g;
    reg->b = b;
    reg->allocated = 0;
}

static void
init_color_registers(ColorRegister *color_registers, int terminal_id)
{
    TRACE(("initializing colors for %d\n", terminal_id));
    {
	unsigned int i;

	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
	    set_sixel_color_register(color_registers, (RegisterNum) i, 0, 0, 0);
	}
    }

    /*
     * 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%
     * dxterm:
     *  ?
     */
    switch (terminal_id) {
    case 125:
    case 241:
	set_sixel_color_register(color_registers, 0, 0, 0, 0);
	set_sixel_color_register(color_registers, 1, 0, 0, 100);
	set_sixel_color_register(color_registers, 2, 0, 100, 0);
	set_sixel_color_register(color_registers, 3, 100, 0, 0);
	break;
    case 240:
    case 330:
	set_sixel_color_register(color_registers, 0, 0, 0, 0);
	set_sixel_color_register(color_registers, 1, 33, 33, 33);
	set_sixel_color_register(color_registers, 2, 66, 66, 66);
	set_sixel_color_register(color_registers, 3, 100, 100, 100);
	break;
    case 340:
    default:
	set_sixel_color_register(color_registers, 0, 0, 0, 0);
	set_sixel_color_register(color_registers, 1, 20, 20, 80);
	set_sixel_color_register(color_registers, 2, 80, 13, 13);
	set_sixel_color_register(color_registers, 3, 20, 80, 20);
	set_sixel_color_register(color_registers, 4, 80, 20, 80);
	set_sixel_color_register(color_registers, 5, 20, 80, 80);
	set_sixel_color_register(color_registers, 6, 80, 80, 20);
	set_sixel_color_register(color_registers, 7, 53, 53, 53);
	set_sixel_color_register(color_registers, 8, 26, 26, 26);
	set_sixel_color_register(color_registers, 9, 33, 33, 60);
	set_sixel_color_register(color_registers, 10, 60, 26, 26);
	set_sixel_color_register(color_registers, 11, 33, 60, 33);
	set_sixel_color_register(color_registers, 12, 60, 33, 60);
	set_sixel_color_register(color_registers, 13, 33, 60, 60);
	set_sixel_color_register(color_registers, 14, 60, 60, 33);
	set_sixel_color_register(color_registers, 15, 80, 80, 80);
	break;
    }
}

static void
init_sixel_graphic(SixelGraphic *graphic, int terminal_id, int private_colors)
{
    TRACE(("initializing sixel graphic\n"));

    graphic->dirty = 1;
    memset(graphic->pixels, 0, sizeof(graphic->pixels));
    memset(graphic->color_registers_used, 0, sizeof(graphic->color_registers_used));

    /*
     * dimensions (REGIS logical, physical):
     * VK100/GIGI  768x4??  768x2??
     * 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)
     * dxterm      ?x?      variable?
     */
    graphic->max_width = BUFFER_WIDTH;
    graphic->max_height = BUFFER_HEIGHT;

    /* default isn't white on the VT240, but not sure what it is */
    graphic->current_register = 3;	/* FIXME: using green, but not sure what it should be */

    /*
     * When an application selects the monochrome map:  the terminal sets the
     * 16 entries of the color map to the default monochrome gray level.
     * Therefore, the original colors are lost when changing from the color map
     * to the monochrome map.
     *
     * If you change the color value (green, red, blue) using the Color Set-Up
     * screen or a ReGIS command, the VT340 sets the gray scale by using the
     * formula (2G + R)/3.
     *
     * When an application selects the color map:  the terminal sets the 16
     * entries of the color map to the default (color) color map.
     */

    /*
     * 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 ? 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)
     * dxterm      ?
     */
    switch (terminal_id) {
    case 125:
	graphic->valid_registers = 4;
	break;
    case 240:
	graphic->valid_registers = 4;
	break;
    case 241:
	graphic->valid_registers = 4;
	break;
    case 330:
	graphic->valid_registers = 4;
	break;
    case 340:
	graphic->valid_registers = 16;
	break;
    default:
	graphic->valid_registers = 64;	/* unknown graphics model -- might as well be generous */
	break;
    }

    /*
     * 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
     * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
     */

    /* FIXME: is this always zero?  what about in light background mode? */
    graphic->device_background = 0;	/* default background color register */

    /* pixel sizes seem to have differed by model and options */
    /* VT240 and VT340 defaulted to 2:1 ratio */
    graphic->aspect_vertical = 2;
    graphic->aspect_horizontal = 1;

    graphic->declared_width = 0;
    graphic->declared_height = 0;

    graphic->actual_width = 0;
    graphic->actual_height = 0;

    graphic->private_colors = private_colors;
    if (graphic->private_colors) {
	TRACE(("sixel using private color registers\n"));
	init_color_registers(graphic->private_color_registers, terminal_id);
	graphic->color_registers = graphic->private_color_registers;
    } else {
	TRACE(("sixel using shared color registers\n"));
	graphic->color_registers = shared_color_registers;
    }

    graphic->charrow = 0;
    graphic->charcol = 0;

    graphic->row = 0;
    graphic->col = 0;

    graphic->valid = 0;
}

static SixelGraphic *
get_sixel_graphic(XtermWidget xw)
{
    TScreen const *screen = TScreenOf(xw);
    int bufferid = screen->whichBuf;
    int terminal_id = screen->terminal_id;
    int private_colors = screen->privatecolorregisters;
    SixelGraphic *graphic;
    unsigned int ii;

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	if (!graphic->valid)
	    break;
    }

    if (ii >= MAX_SIXEL_GRAPHICS) {
	int min_charrow = 0;
	SixelGraphic *min_graphic = NULL;

	for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	    graphic = &sixel_graphics[ii];
	    if (!min_graphic || graphic->charrow < min_charrow) {
		min_charrow = graphic->charrow;
		min_graphic = graphic;
	    }
	}
	graphic = min_graphic;
    }

    graphic->xw = xw;
    graphic->bufferid = bufferid;
    graphic->id = next_sixel_id++;
    init_sixel_graphic(graphic, terminal_id, private_colors);
    return graphic;
}

static void
dump_sixel(SixelGraphic const *graphic)
{
#ifdef DUMP_SIXEL_BITMAP
    int r, c;
    RegisterNum color;
    ColorRegister const *reg;
#endif

    (void) graphic;

    TRACE(("sixel stats: charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
	   graphic->charrow,
	   graphic->charcol,
	   graphic->actual_width,
	   graphic->actual_height,
	   graphic->pixw,
	   graphic->pixh));

#ifdef DUMP_SIXEL_BITMAP
    TRACE(("sixel dump:\n"));
    for (r = 0; r < graphic->max_height; r++) {
	for (c = 0; c < graphic->max_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"));
    }
#endif
    TRACE(("\n"));
}

static void
set_shared_color_register(RegisterNum color, short r, short g, short b)
{
    SixelGraphic *graphic;
    unsigned int ii;

    assert(color < MAX_COLOR_REGISTERS);

    set_sixel_color_register(shared_color_registers, color, r, g, b);

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	if (graphic->private_colors)
	    continue;

	if (graphic->color_registers_used[ii]) {
	    graphic->dirty = 1;
	}
    }
}

#define ScaleForXColor(s) (unsigned short) ((long)(s) * 65535 / 100)

static Pixel
sixel_register_to_xpixel(ColorRegister *reg, XtermWidget xw)
{
    if (!reg->allocated) {
	XColor def;

	def.red = ScaleForXColor(reg->r);
	def.green = ScaleForXColor(reg->g);
	def.blue = ScaleForXColor(reg->b);
	def.flags = DoRed | DoGreen | DoBlue;
	if (!allocateBestRGB(xw, &def)) {
	    return 0UL;
	}
	reg->pix = def.pixel;
	reg->allocated = 1;
    }

    /* FIXME: with so many possible colors we need to determine
     * when to free them to be nice to PseudoColor displays
     */
    return reg->pix;
}

static void
refresh_sixel_graphic(TScreen const *screen,
		      SixelGraphic *graphic,
		      int xbase,
		      int ybase,
		      int x,
		      int y,
		      int w,
		      int h)
{
    Display *display = screen->display;
    Window vwindow = WhichVWin(screen)->window;
    GC graphics_gc;
    int r, c;
    int wx, wy;
    int pw, ph;
    RegisterNum color;
    RegisterNum old_fg;
    XGCValues xgcv;
    XtGCMask mask;
    int holes, total;

    TRACE(("refreshing sixel graphic from %d,%d %dx%d (valid=%d, bg=%dx%d size=%dx%d, max=%dx%d) at base=%d,%d\n",
	   x, y, w, h,
	   graphic->valid,
	   graphic->declared_width,
	   graphic->declared_height,
	   graphic->actual_width,
	   graphic->actual_height,
	   graphic->max_width,
	   graphic->max_height,
	   xbase, ybase));

    memset(&xgcv, 0, sizeof(xgcv));
    xgcv.foreground = 0UL;
    xgcv.graphics_exposures = False;
    mask = GCForeground | GCGraphicsExposures;
    graphics_gc = XCreateGC(display, vwindow, mask, &xgcv);

    pw = graphic->pixw;
    ph = graphic->pixh;

    TRACE(("refreshed graphic covers 0,0 to %d,%d\n",
	   (graphic->actual_width - 1) * pw + pw - 1,
	   (graphic->actual_height - 1) * ph + ph - 1));
    TRACE(("refreshed area covers %d,%d to %d,%d\n",
	   x, y,
	   x + w - 1,
	   y + h - 1));

    old_fg = COLOR_HOLE;
    holes = total = 0;
    for (r = 0; r < graphic->actual_height; r++)
	for (c = 0; c < graphic->actual_width; c++) {
	    if (r * ph + ph - 1 < y ||
		r * ph > y + h - 1 ||
		c * pw + pw - 1 < x ||
		c * pw > x + w - 1)
		continue;

	    wy = ybase + r * ph;
	    wx = xbase + c * pw;

	    total++;
	    color = graphic->pixels[r * graphic->max_width + c];
	    if (color == COLOR_HOLE) {
		holes++;
		continue;
	    }

	    if (color != old_fg) {
		xgcv.foreground =
		    sixel_register_to_xpixel(&graphic->color_registers[color],
					     graphic->xw);
		XChangeGC(display, graphics_gc, mask, &xgcv);
		old_fg = color;
	    }

	    XFillRectangle(display, vwindow, graphics_gc,
			   wx, wy, (unsigned) pw, (unsigned) ph);
	}

#ifdef DEBUG_REFRESH
    {
	XColor def;

	def.red = (short) (1.0 * 65535.0);
	def.green = (short) (0.1 * 65535.0);
	def.blue = (short) (1.0 * 65535.0);
	def.flags = DoRed | DoGreen | DoBlue;
	if (allocateBestRGB(graphic->xw, &def)) {
	    xgcv.foreground = def.pixel;
	    XChangeGC(display, graphics_gc, mask, &xgcv);
	}
	XFillRectangle(display, vwindow, graphics_gc,
		       xbase + 0,
		       ybase + 0,
		       (unsigned) pw, (unsigned) ph);
	XFillRectangle(display, vwindow, graphics_gc,
		       xbase + (graphic->actual_width - 1) * pw,
		       ybase + (graphic->actual_height - 1) * ph,
		       (unsigned) pw, (unsigned) ph);

	def.red = (unsigned short) ((1.0 - 0.1 * (rand() / (double)
						  RAND_MAX) * 65535.0));
	def.green = (unsigned short) ((0.7 + 0.2 * (rand() / (double)
						    RAND_MAX)) * 65535.0);
	def.blue = (unsigned short) ((0.1 + 0.1 * (rand() / (double)
						   RAND_MAX)) * 65535.0);
	def.flags = DoRed | DoGreen | DoBlue;
	if (allocateBestRGB(graphic->xw, &def)) {
	    xgcv.foreground = def.pixel;
	    XChangeGC(display, graphics_gc, mask, &xgcv);
	}
	XDrawLine(display, vwindow, graphics_gc,
		  xbase + x + 0, ybase + y + 0,
		  xbase + x + w - 1, ybase + y + 0);
	XDrawLine(display, vwindow, graphics_gc,
		  xbase + x + w - 1, ybase + y + 0,
		  xbase + x + 0, ybase + y + h - 1);
	XDrawLine(display, vwindow, graphics_gc,
		  xbase + x + 0, ybase + y + h - 1,
		  xbase + x + w - 1, ybase + y + h - 1);
	XDrawLine(display, vwindow, graphics_gc,
		  xbase + x + w - 1, ybase + y + h - 1,
		  xbase + x + 0, ybase + y + 0);
    }
#endif
    XFlush(display);
    TRACE(("done refreshing sixel graphic: %d of %d refreshed pixels were holes\n",
	   holes, total));

    XFreeGC(display, graphics_gc);
}

/*
 * Primary color hues:
 *  blue:    0 degrees
 *  red:   120 degrees
 *  green: 240 degrees
 */
static void
hls2rgb(int h, int l, int s, short *r, short *g, short *b)
{
    double hs = (h + 240) % 360;
    double hv = hs / 360.0;
    double lv = l / 100.0;
    double sv = s / 100.0;
    double c, x, m;
    double r1, g1, b1;
    int hpi;

    if (s == 0) {
	*r = *g = *b = (short) l;
	return;
    }

    c = (1.0 - fabs(2.0 * lv - 1.0)) * sv;
    hpi = (int) (hv * 6.0);
    x = (hpi & 1) ? c : 0.0;
    m = lv - 0.5 * c;

    switch (hpi) {
    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:
	printf("BAD\n");
	*r = (short) 100;
	*g = (short) 100;
	*b = (short) 100;
	return;
    }

    *r = (short) ((r1 + m) * 100.0 + 0.5);
    *g = (short) ((g1 + m) * 100.0 + 0.5);
    *b = (short) ((b1 + m) * 100.0 + 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;
}

static void
update_sixel_aspect(SixelGraphic *graphic)
{
    /* We want to keep the ratio accurate but would like every pixel to have
     * the same size so keep these as whole numbers.
     */
    /* FIXME: DEC terminals had pixels about twice as tall as they were wide,
     * and it seems the VT125 and VT24x only used data from odd graphic rows.
     * This means it basically cancels out if we ignore both, except that
     * the even rows of pixels may not be written by the application such that
     * they are suitable for display.  In practice this doesn't seem to be
     * an issue but I have very few test files/programs.
     */
    if (graphic->aspect_vertical < graphic->aspect_horizontal) {
	graphic->pixw = 1;
	graphic->pixh = ((graphic->aspect_vertical
			  + graphic->aspect_horizontal - 1)
			 / graphic->aspect_horizontal);
    } else {
	graphic->pixw = ((graphic->aspect_horizontal
			  + graphic->aspect_vertical - 1)
			 / graphic->aspect_vertical);
	graphic->pixh = 1;
    }
    TRACE(("sixel aspect ratio: an=%d ad=%d -> pixw=%d pixh=%d\n",
	   graphic->aspect_vertical,
	   graphic->aspect_horizontal,
	   graphic->pixw,
	   graphic->pixh));
}

/*
 * Interpret sixel graphics sequences.
 *
 * Resources:
 *  http://en.wikipedia.org/wiki/Sixel
 *  http://vt100.net/docs/vt3xx-gp/chapter14.html
 *  ftp://ftp.cs.utk.edu/pub/shuford/terminal/sixel_graphics_news.txt
 *  ftp://ftp.cs.utk.edu/pub/shuford/terminal/all_about_sixels.txt
 */
extern void
parse_sixel(XtermWidget xw, ANSI *params, char const *string)
{
    TScreen *screen = TScreenOf(xw);
    SixelGraphic *graphic;
    Char ch;

    graphic = get_sixel_graphic(xw);

    {
	int Pmacro = params->a_param[0];
	int Pbgmode = params->a_param[1];
	int Phgrid = params->a_param[2];
	int Pan = params->a_param[3];
	int Pad = params->a_param[4];
	int Ph = params->a_param[5];
	int Pv = params->a_param[6];

	(void) Phgrid;

	TRACE(("sixel bitmap graphics sequence: params=%d (Pmacro=%d Pbgmode=%d Phgrid=%d) scroll_amt=%d\n",
	       params->a_nparam,
	       Pmacro,
	       Pbgmode,
	       Phgrid,
	       screen->scroll_amt));

	switch (params->a_nparam) {
	case 7:
	    if (Pan == 0 || Pad == 0) {
		TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
		return;
	    }
	    graphic->aspect_vertical = Pan;
	    graphic->aspect_horizontal = Pad;

	    if (Ph == 0 || Pv == 0) {
		TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
		       Ph, Pv));
		return;
	    }
	    if (Ph > graphic->max_width || Pv > graphic->max_height) {
		TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
		       Ph, Pv));
		return;
	    }
	    graphic->declared_width = Ph;
	    graphic->declared_height = Pv;
	    if (graphic->declared_width > graphic->actual_width) {
		graphic->actual_width = graphic->declared_width;
	    }
	    if (graphic->declared_height > graphic->actual_height) {
		graphic->actual_height = graphic->declared_height;
	    }
	    break;
	case 3:
	case 2:
	    switch (Pmacro) {
	    case 0:
	    case 1:
		graphic->aspect_vertical = 5;
		graphic->aspect_horizontal = 1;
		break;
	    case 2:
		graphic->aspect_vertical = 3;
		graphic->aspect_horizontal = 1;
		break;
	    case 3:
	    case 4:
		graphic->aspect_vertical = 2;
		graphic->aspect_horizontal = 1;
		break;
	    case 5:
	    case 6:
		graphic->aspect_vertical = 2;
		graphic->aspect_horizontal = 1;
		break;
	    case 7:
	    case 8:
	    case 9:
		graphic->aspect_vertical = 1;
		graphic->aspect_horizontal = 1;
		break;
	    default:
		TRACE(("DATA_ERROR: unknown sixel macro mode parameter\n"));
		return;
	    }
	    break;
	case 0:
	    break;
	default:
	    TRACE(("DATA_ERROR: unexpected parameter count (found %d)\n", params->a_nparam));
	    return;
	}

	if (Pbgmode == 1) {
	    graphic->background = COLOR_HOLE;
	} else {
	    graphic->background = graphic->device_background;
	}

	/* Ignore the grid parameter because it seems only printers paid attention to it.
	 * The VT3xx was always 0.0195 cm.
	 */
    }

#if OPT_SIXEL_GRAPHICS
    if (xw->keyboard.flags & MODE_DECSDM) {
	TRACE(("sixel scrolling enabled: inline positioning for graphic\n"));
	graphic->charrow = screen->cur_row;
	graphic->charcol = screen->cur_col;
    }

    update_sixel_aspect(graphic);
#endif

    for (;;) {
	ch = CharOf(*string);
	if (ch == '\0')
	    break;

	if (ch >= 0x3f && ch <= 0x7e) {
	    int sixel = ch - 0x3f;
	    TRACE(("sixel=%x (%c)\n", sixel, (char) ch));
	    if (!graphic->valid) {
		init_sixel_background(graphic);
		graphic->valid = 1;
	    }
	    set_sixel(graphic, sixel);
	    graphic->col++;
	} else if (ch == '$') {	/* DECGCR */
	    /* ignore DECCRNLM in sixel mode */
	    TRACE(("sixel CR\n"));
	    graphic->col = 0;
	} else if (ch == '-') {	/* DECGNL */
	    int scroll_lines;
	    TRACE(("sixel NL\n"));
	    scroll_lines = 0;
	    while (graphic->charrow - scroll_lines +
		   (((graphic->row + 6) * graphic->pixh
		     + FontHeight(screen) - 1)
		    / FontHeight(screen)) > screen->bot_marg) {
		scroll_lines++;
	    }
	    graphic->col = 0;
	    graphic->row += 6;
	    /* If we hit the bottom margin on the graphics page (well, we just use the text margin for now),
	     * the behavior is to either scroll or to discard the remainder of the graphic depending on this
	     * setting.
	     */
	    if (scroll_lines > 0) {
		if (xw->keyboard.flags & MODE_DECSDM) {
		    Display *display = screen->display;
		    xtermScroll(xw, scroll_lines);
		    XSync(display, False);
		    TRACE(("graphic scrolled the screen %d lines. screen->scroll_amt=%d screen->topline=%d, now starting row is %d\n",
			   scroll_lines,
			   screen->scroll_amt,
			   screen->topline,
			   graphic->charrow));
		} else {
		    break;
		}
	    }
	} else if (ch == '!') {	/* DECGRI */
	    int Pcount;
	    const char *start;
	    int sixel;
	    int i;

	    start = ++string;
	    for (;;) {
		ch = CharOf(*string);
		if (ch != '0' &&
		    ch != '1' &&
		    ch != '2' &&
		    ch != '3' &&
		    ch != '4' &&
		    ch != '5' &&
		    ch != '6' &&
		    ch != '7' &&
		    ch != '8' &&
		    ch != '9' &&
		    ch != ' ' &&
		    ch != '\r' &&
		    ch != '\n')
		    break;
		string++;
	    }
	    if (ch == '\0') {
		TRACE(("DATA_ERROR: sixel data string terminated in the middle of a repeat operator\n"));
		return;
	    }
	    if (string == start) {
		TRACE(("DATA_ERROR: sixel data string contains a repeat operator with empty count\n"));
		return;
	    }
	    Pcount = atoi(start);
	    sixel = ch - 0x3f;
	    TRACE(("sixel repeat operator: sixel=%d (%c), count=%d\n",
		   sixel, (char) ch, Pcount));
	    if (!graphic->valid) {
		init_sixel_background(graphic);
		graphic->valid = 1;
	    }
	    for (i = 0; i < Pcount; i++) {
		set_sixel(graphic, sixel);
		graphic->col++;
	    }
	} else if (ch == '#') {	/* DECGCI */
	    ANSI color_params;
	    int Pregister;

	    parse_prefixedtype_params(&color_params, &string);
	    Pregister = color_params.a_param[0];
	    if (Pregister >= graphic->valid_registers) {
		TRACE(("DATA_WARNING: sixel color operator uses out-of-range register %d\n", Pregister));
		/* FIXME: supposedly the DEC terminals wrapped register indicies -- verify */
		while (Pregister >= graphic->valid_registers)
		    Pregister -= graphic->valid_registers;
		TRACE(("DATA_WARNING: converted to %d\n", Pregister));
	    }

	    if (color_params.a_nparam > 2 && color_params.a_nparam <= 5) {
		int Pspace = color_params.a_param[1];
		int Pc1 = color_params.a_param[2];
		int Pc2 = color_params.a_param[3];
		int Pc3 = color_params.a_param[4];
		short r, g, b;

		TRACE(("sixel set color register=%d space=%d color=[%d,%d,%d] (nparams=%d)\n",
		       Pregister, Pspace, Pc1, Pc2, Pc3, color_params.a_nparam));

		switch (Pspace) {
		case 1:	/* HLS */
		    if (Pc1 > 360 || Pc2 > 100 || Pc3 > 100) {
			TRACE(("DATA_ERROR: sixel set color operator uses out-of-range HLS color coordinates %d,%d,%d\n",
			       Pc1, Pc2, Pc3));
			return;
		    }
		    hls2rgb(Pc1, Pc2, Pc3, &r, &g, &b);
		    break;
		case 2:	/* RGB */
		    if (Pc1 > 100 || Pc2 > 100 || Pc3 > 100) {
			TRACE(("DATA_ERROR: sixel set color operator uses out-of-range RGB color coordinates %d,%d,%d\n",
			       Pc1, Pc2, Pc3));
			return;
		    }
		    r = (short) Pc1;
		    g = (short) Pc2;
		    b = (short) Pc3;
		    break;
		default:	/* unknown */
		    TRACE(("DATA_ERROR: sixel set color operator uses unknown color space %d\n", Pspace));
		    return;
		}
		if (graphic->private_colors) {
		    set_sixel_color_register(graphic->private_color_registers,
					     (RegisterNum) Pregister,
					     r, g, b);
		} else {
		    set_shared_color_register((RegisterNum) Pregister, r, g, b);
		}
		graphic->color_registers_used[Pregister] = 1;
	    } else if (color_params.a_nparam == 1) {
		TRACE(("sixel switch to color register=%d (nparams=%d)\n",
		       Pregister, color_params.a_nparam));
		graphic->current_register = (RegisterNum) Pregister;
	    } else {
		TRACE(("DATA_ERROR: sixel switch color operator with unexpected parameter count (nparams=%d)\n", color_params.a_nparam));
		return;
	    }
	    continue;
	} else if (ch == '"') /* DECGRA */  {
	    ANSI raster_params;

	    parse_prefixedtype_params(&raster_params, &string);
	    if (raster_params.a_nparam < 2) {
		TRACE(("DATA_ERROR: sixel raster attribute operator with incomplete parameters (found %d, expected 2 or 4)\n", raster_params.a_nparam));
		return;
	    } {
		int Pan = raster_params.a_param[0];
		int Pad = raster_params.a_param[1];
		TRACE(("sixel raster attribute with h:w=%d:%d\n", Pan, Pad));
		if (Pan == 0 || Pad == 0) {
		    TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
		    return;
		}
		graphic->aspect_vertical = Pan;
		graphic->aspect_horizontal = Pad;
		update_sixel_aspect(graphic);
	    }

	    if (raster_params.a_nparam >= 4) {
		int Ph = raster_params.a_param[2];
		int Pv = raster_params.a_param[3];

		TRACE(("sixel raster attribute with h=%d v=%d\n", Ph, Pv));
		if (Ph == 0 || Pv == 0) {
		    TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
			   Ph, Pv));
		    return;
		}
		if (Ph > graphic->max_width || Pv > graphic->max_height) {
		    TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
			   Ph, Pv));
		    return;
		}
		graphic->declared_width = Ph;
		graphic->declared_height = Pv;
		if (graphic->declared_width > graphic->actual_width) {
		    graphic->actual_width = graphic->declared_width;
		}
		if (graphic->declared_height > graphic->actual_height) {
		    graphic->actual_height = graphic->declared_height;
		}
	    }

	    continue;
	} else if (ch == ' ' || ch == '\r' || ch == '\n') {
	    /* EMPTY */ ;
	} else {
	    TRACE(("DATA_ERROR: unknown sixel command %04x (%c)\n",
		   (int) ch, ch));
	}

	string++;
    }

    /* update the screen */
    if (screen->scroll_amt)
	FlushScroll(xw);

    if (xw->keyboard.flags & MODE_DECSDM) {
	int new_row = (graphic->charrow
		       + ((graphic->actual_height * graphic->pixh)
			  / FontHeight(screen)));
	int new_col = (graphic->charcol
		       + (((graphic->actual_width * graphic->pixw)
			   + FontWidth(screen) - 1)
			  / FontWidth(screen)));

	TRACE(("setting text position after %dx%d graphic starting on row=%d col=%d: cursor new_row=%d new_col=%d\n",
	       graphic->actual_width * graphic->pixw,
	       graphic->actual_height * graphic->pixh,
	       graphic->charrow,
	       graphic->charcol,
	       new_row, new_col));

	if (new_col > screen->rgt_marg) {
	    new_col = screen->lft_marg;
	    new_row++;
	    TRACE(("column past left margin, overriding to row=%d col=%d\n",
		   new_row, new_col));
	}

	while (new_row > screen->bot_marg) {
	    xtermScroll(xw, 1);
	    new_row--;
	    TRACE(("bottom row was past screen.  new start row=%d, cursor row=%d\n",
		   graphic->charrow, new_row));
	}

	if (new_row < 0) {
	    TRACE(("new row is going to be negative (%d)!", new_row));	/* FIXME: this was triggering, now it isn't */
	}
	set_cur_row(screen, new_row);
	set_cur_col(screen, new_col <= screen->rgt_marg ? new_col : screen->rgt_marg);
    }

    refresh_modified_displayed_graphics(screen);

    TRACE(("DONE successfully parsed sixel data\n"));
    dump_sixel(graphic);
}

extern void
parse_regis(XtermWidget xw, ANSI *params, char const *string)
{
    (void) xw;
    (void) string;
    (void) params;

    TRACE(("ReGIS vector graphics mode, params=%d\n", params->a_nparam));
}

/* Erase the portion of any displayed graphic overlapping with a rectangle
 * of the given size and location in pixels.
 * This is used to allow text to "erase" graphics underneath it.
 */
static void
erase_sixel_graphic(SixelGraphic *graphic, int x, int y, int w, int h)
{
    RegisterNum hole = COLOR_HOLE;
    int pw, ph;
    int r, c;

    pw = graphic->pixw;
    ph = graphic->pixh;

    TRACE(("erasing sixel bitmap %d,%d %dx%d\n", x, y, w, h));

    for (r = 0; r < graphic->actual_height; r++) {
	for (c = 0; c < graphic->actual_width; c++) {
	    if (r * ph + ph - 1 < y ||
		r * ph > y + h - 1 ||
		c * pw + pw - 1 < x ||
		c * pw > x + w - 1)
		continue;

	    graphic->pixels[r * graphic->max_width + c] = hole;
	}
    }
}

static int
compare_sixel_ids(const void *left, const void *right)
{
    const SixelGraphic *l = *(const SixelGraphic *const *) left;
    const SixelGraphic *r = *(const SixelGraphic *const *) right;

    if (!l->valid || !r->valid)
	return 0;
    if (l->id < r->id)
	return -1;
    else
	return 1;
}

extern void
refresh_displayed_graphics(TScreen const *screen,
			   int leftcol,
			   int toprow,
			   int ncols,
			   int nrows)
{
    SixelGraphic *ordered_graphics[MAX_SIXEL_GRAPHICS];
    SixelGraphic *graphic;
    unsigned int ii;
    int x, y, w, h;
    int xbase, ybase;

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	ordered_graphics[ii] = &sixel_graphics[ii];
    }
    qsort(ordered_graphics,
	  MAX_SIXEL_GRAPHICS,
	  sizeof(ordered_graphics[0]),
	  compare_sixel_ids);

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = ordered_graphics[ii];
	if (!graphic->valid || graphic->bufferid != screen->whichBuf)
	    continue;

	x = (leftcol - graphic->charcol) * FontWidth(screen);
	y = (toprow - graphic->charrow) * FontHeight(screen);
	w = ncols * FontWidth(screen);
	h = nrows * FontHeight(screen);

	xbase = (screen->border
		 + graphic->charcol * FontWidth(screen));
	ybase = (screen->border
		 + (graphic->charrow - screen->topline) * FontHeight(screen));

	if (xbase + x + w + screen->border > FullWidth(screen))
	    w = FullWidth(screen) - (xbase + x + screen->border);
	if (ybase + y + h + screen->border > FullHeight(screen))
	    h = FullHeight(screen) - (ybase + y + screen->border);
	else if (ybase + y < screen->border) {
	    int diff = screen->border - (ybase + y);
	    y += diff;
	    h -= diff;
	}

	TRACE(("graphics refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d xbase=%d ybase=%d\n",
	       screen->topline,
	       leftcol, toprow,
	       nrows, ncols,
	       x, y, w, h,
	       xbase, ybase));
	refresh_sixel_graphic(screen, graphic, xbase, ybase, x, y, w, h);
    }
}

extern void
refresh_modified_displayed_graphics(TScreen const *screen)
{
    SixelGraphic *graphic;
    unsigned int ii;
    int leftcol, toprow;
    int nrows, ncols;
    int x, y, w, h;
    int xbase, ybase;

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	if (!graphic->valid || graphic->bufferid != screen->whichBuf || !graphic->dirty)
	    continue;

	leftcol = graphic->charcol;
	toprow = graphic->charrow;
	nrows = (((graphic->actual_height * graphic->pixh)
		  + FontHeight(screen) - 1)
		 / FontHeight(screen));
	ncols = (((graphic->actual_width * graphic->pixw)
		  + FontWidth(screen) - 1)
		 / FontWidth(screen));

	x = (leftcol - graphic->charcol) * FontWidth(screen);
	y = (toprow - graphic->charrow) * FontHeight(screen);
	w = ncols * FontWidth(screen);
	h = nrows * FontHeight(screen);

	xbase = (screen->border
		 + graphic->charcol * FontWidth(screen));
	ybase = (screen->border
		 + (graphic->charrow - screen->topline) * FontHeight(screen));

	if (xbase + x + w + screen->border > FullWidth(screen))
	    w = FullWidth(screen) - (xbase + x + screen->border);
	if (ybase + y + h + screen->border > FullHeight(screen))
	    h = FullHeight(screen) - (ybase + y + screen->border);
	else if (ybase + y < screen->border) {
	    int diff = screen->border - (ybase + y);
	    y += diff;
	    h -= diff;
	}

	TRACE(("full graphics refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d xbase=%d ybase=%d\n",
	       screen->topline,
	       leftcol, toprow,
	       nrows, ncols,
	       x, y, w, h,
	       xbase, ybase));
	refresh_sixel_graphic(screen, graphic, xbase, ybase, x, y, w, h);
	graphic->dirty = 0;
    }
}

extern void
scroll_displayed_graphics(int rows)
{
    SixelGraphic *graphic;
    unsigned int ii;

    TRACE(("graphics scroll: moving all up %d rows\n", rows));
    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	if (!graphic->valid)
	    continue;

	graphic->charrow -= rows;
    }
}

extern void
pixelarea_clear_displayed_graphics(TScreen const *screen,
				   int winx,
				   int winy,
				   int w,
				   int h)
{
    SixelGraphic *graphic;
    unsigned int ii;
    int x, y;

    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	if (!graphic->valid)
	    continue;

	x = winx - graphic->charcol * FontWidth(screen);
	y = winy - graphic->charrow * FontHeight(screen);

	TRACE(("pixelarea graphics erase: 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_sixel_graphic(graphic, x, y, w, h);
    }
}

extern void
chararea_clear_displayed_graphics(TScreen const *screen,
				  int leftcol,
				  int toprow,
				  int ncols,
				  int nrows)
{
    int x, y, w, h;

    x = leftcol * FontWidth(screen);
    y = toprow * FontHeight(screen);
    w = ncols * FontWidth(screen);
    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);
}

extern void
reset_displayed_graphics(TScreen const *screen)
{
    SixelGraphic *graphic;
    unsigned int ii;

    init_color_registers(shared_color_registers, screen->terminal_id);
    TRACE(("resetting all sixel graphics\n"));
    for (ii = 0U; ii < MAX_SIXEL_GRAPHICS; ii++) {
	graphic = &sixel_graphics[ii];
	graphic->valid = 0;
    }
}
