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