diff options
Diffstat (limited to 'app/xterm/graphics.c')
-rw-r--r-- | app/xterm/graphics.c | 1475 |
1 files changed, 1475 insertions, 0 deletions
diff --git a/app/xterm/graphics.c b/app/xterm/graphics.c new file mode 100644 index 000000000..9bdc50c02 --- /dev/null +++ b/app/xterm/graphics.c @@ -0,0 +1,1475 @@ +/* $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; + } +} |