/* $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 #include #include #include #include #include #include #include #include #include #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; } }