graphics.c revision a5ae21e4
1a5ae21e4Smrg/* $XTermId: graphics.c,v 1.95 2021/09/19 18:57:09 tom Exp $ */
2e0a2b6dfSmrg
3e0a2b6dfSmrg/*
4ad37e533Smrg * Copyright 2013-2020,2021 by Ross Combs
5ad37e533Smrg * Copyright 2013-2020,2021 by Thomas E. Dickey
6e0a2b6dfSmrg *
7e0a2b6dfSmrg *                         All Rights Reserved
8e0a2b6dfSmrg *
9e0a2b6dfSmrg * Permission is hereby granted, free of charge, to any person obtaining a
10e0a2b6dfSmrg * copy of this software and associated documentation files (the
11e0a2b6dfSmrg * "Software"), to deal in the Software without restriction, including
12e0a2b6dfSmrg * without limitation the rights to use, copy, modify, merge, publish,
13e0a2b6dfSmrg * distribute, sublicense, and/or sell copies of the Software, and to
14e0a2b6dfSmrg * permit persons to whom the Software is furnished to do so, subject to
15e0a2b6dfSmrg * the following conditions:
16e0a2b6dfSmrg *
17e0a2b6dfSmrg * The above copyright notice and this permission notice shall be included
18e0a2b6dfSmrg * in all copies or substantial portions of the Software.
19e0a2b6dfSmrg *
20e0a2b6dfSmrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21e0a2b6dfSmrg * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22e0a2b6dfSmrg * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23e0a2b6dfSmrg * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
24e0a2b6dfSmrg * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25e0a2b6dfSmrg * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26e0a2b6dfSmrg * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27e0a2b6dfSmrg *
28e0a2b6dfSmrg * Except as contained in this notice, the name(s) of the above copyright
29e0a2b6dfSmrg * holders shall not be used in advertising or otherwise to promote the
30e0a2b6dfSmrg * sale, use or other dealings in this Software without prior written
31e0a2b6dfSmrg * authorization.
32e0a2b6dfSmrg */
33e0a2b6dfSmrg
34e0a2b6dfSmrg#include <xterm.h>
35e0a2b6dfSmrg
36e0a2b6dfSmrg#include <stdio.h>
37e0a2b6dfSmrg#include <ctype.h>
38e0a2b6dfSmrg#include <stdlib.h>
39e0a2b6dfSmrg
40e0a2b6dfSmrg#include <data.h>
41e0a2b6dfSmrg#include <ptyx.h>
42e0a2b6dfSmrg
43e0a2b6dfSmrg#include <assert.h>
44e0a2b6dfSmrg#include <graphics.h>
45e0a2b6dfSmrg
46894e0ac8Smrg#undef DUMP_BITMAP
47894e0ac8Smrg#undef DUMP_COLORS
48894e0ac8Smrg#undef DEBUG_PALETTE
49894e0ac8Smrg#undef DEBUG_PIXEL
50e0a2b6dfSmrg#undef DEBUG_REFRESH
51e0a2b6dfSmrg
5201037d57Smrg/*
5301037d57Smrg * graphics TODO list
5401037d57Smrg *
55e0a2b6dfSmrg * ReGIS:
562e4f8982Smrg * - ship a default alphabet zero font instead of scaling Xft fonts
57913cc679Smrg * - input cursors
58913cc679Smrg * - output cursors
59913cc679Smrg * - mouse/tablet/arrow-key input
602e4f8982Smrg * - fix graphic pages for ReGIS -- they should also apply to text and sixel graphics
6101037d57Smrg * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
6201037d57Smrg * - non-ASCII alphabets
632e4f8982Smrg * - enter/leave anywhere in a command
642e4f8982Smrg * - locator key definitions (DECLKD)
65894e0ac8Smrg * - command display mode
66913cc679Smrg * - re-rasterization on window resize
67894e0ac8Smrg * - macros
6801037d57Smrg * - improved fills for narrow angles (track actual lines not just pixels)
69913cc679Smrg * - hardcopy/screen-capture support (need dialog of some sort for safety)
70913cc679Smrg * - error reporting
7101037d57Smrg *
72e0a2b6dfSmrg * sixel:
73894e0ac8Smrg * - fix problem where new_row < 0 during sixel parsing (see FIXME)
7401037d57Smrg * - screen-capture support (need dialog of some sort for safety)
7501037d57Smrg *
76894e0ac8Smrg * VT55/VT105 waveform graphics
77894e0ac8Smrg * - everything
7801037d57Smrg *
7901037d57Smrg * Tektronix:
8001037d57Smrg * - color (VT340 4014 emulation, 41xx, IRAF GTERM, and also MS-DOS Kermit color support)
8101037d57Smrg * - polygon fill (41xx)
8201037d57Smrg * - clear area extension
8301037d57Smrg * - area fill extension
8401037d57Smrg * - pixel operations (RU/RS/RP)
8501037d57Smrg * - research other 41xx and 42xx extensions
8601037d57Smrg *
8701037d57Smrg * common graphics features:
88894e0ac8Smrg * - handle light/dark screen modes (CSI?5[hl])
89894e0ac8Smrg * - update text fg/bg color which overlaps images
9001037d57Smrg * - handle graphic updates in scroll regions (verify effect on graphics)
91894e0ac8Smrg * - handle rectangular area copies (verify they work with graphics)
9201037d57Smrg * - invalidate graphics under graphic if same origin, at least as big, and bg not transparent
9301037d57Smrg * - invalidate graphic if completely scrolled past end of scrollback
9401037d57Smrg * - invalidate graphic if all pixels are transparent/erased
9501037d57Smrg * - invalidate graphic if completely scrolled out of alt buffer
9601037d57Smrg * - posturize requested colors to match hardware palettes (e.g. only four possible shades on VT240)
97894e0ac8Smrg * - color register report/restore
9801037d57Smrg * - ability to select/copy graphics for pasting in other programs
9901037d57Smrg * - ability to show non-scroll-mode sixel graphics in a separate window
10001037d57Smrg * - ability to show ReGIS graphics in a separate window
10101037d57Smrg * - ability to show Tektronix graphics in VT100 window
1022e4f8982Smrg * - truncate graphics at bottom edge of terminal?
1032e4f8982Smrg * - locator events (DECEFR DECSLE DECELR DECLRP)
1042e4f8982Smrg * - locator controller mode (CSI6i / CSI7i)
10501037d57Smrg *
10601037d57Smrg * new escape sequences:
10701037d57Smrg * - way to query text font size without "window ops" (or make "window ops" permissions more fine grained)
108894e0ac8Smrg * - way to query and set the number of graphics pages
10901037d57Smrg *
110894e0ac8Smrg * ReGIS extensions:
11101037d57Smrg * - non-integer text scaling
112913cc679Smrg * - free distortionless text rotation (vs. simulating the distortion and aligning to 45deg increments)
11301037d57Smrg * - font characteristics: bold/underline/italic
11401037d57Smrg * - remove/increase arbitrary limits (pattern size, pages, alphabets, stack size, font names, etc.)
11501037d57Smrg * - shade/fill with borders
11601037d57Smrg * - sprites (copy portion of page into/out of buffer with scaling and rotation)
11701037d57Smrg * - ellipses
11801037d57Smrg * - 2D patterns
11901037d57Smrg * - option to set actual graphic size (not just coordinate range)
12001037d57Smrg * - gradients (for lines and fills)
121894e0ac8Smrg * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
122894e0ac8Smrg * - transparency
123894e0ac8Smrg * - background color as stackable write control
124894e0ac8Smrg * - true color (virtual color registers created upon lookup)
125894e0ac8Smrg * - anti-aliasing
126913cc679Smrg * - variable-width (proportional) text
127e0a2b6dfSmrg */
128e0a2b6dfSmrg
129e0a2b6dfSmrg/* font sizes:
130e0a2b6dfSmrg * VT510:
131e0a2b6dfSmrg *   80 Columns 132 Columns Maximum Number of Lines
132e0a2b6dfSmrg *   10 x 16   6 x 16  26 lines + keyboard indicator line
133894e0ac8Smrg *   10 x 13   6 x 13  26 lines + keyboard indicator line
134e0a2b6dfSmrg *   10 x 10   6 x 10  42 lines + keyboard indicator line
135e0a2b6dfSmrg *   10 x 8    6 x 8   53 lines + keyboard indicator line
13601037d57Smrg */
13701037d57Smrg
13801037d57Smrgtypedef struct allocated_color_register {
13901037d57Smrg    struct allocated_color_register *next;
14001037d57Smrg    Pixel pix;
14101037d57Smrg    short r, g, b;
14201037d57Smrg} AllocatedColorRegister;
14301037d57Smrg
14401037d57Smrg#define LOOKUP_WIDTH 16
14501037d57Smrgstatic AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
146e0a2b6dfSmrg
147894e0ac8Smrg#define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
148894e0ac8Smrg
149894e0ac8Smrgstatic ColorRegister *shared_color_registers;
150894e0ac8Smrgstatic Graphic *displayed_graphics[MAX_GRAPHICS];
151894e0ac8Smrgstatic unsigned next_graphic_id = 0U;
152f2e35a3aSmrgstatic unsigned used_graphics;	/* 0 to MAX_GRAPHICS */
153f2e35a3aSmrg
154f2e35a3aSmrg#define DiffColor(this,that) \
155f2e35a3aSmrg	(this.r != that.r || \
156f2e35a3aSmrg	 this.g != that.g || \
157f2e35a3aSmrg	 this.b != that.b)
158f2e35a3aSmrg
159f2e35a3aSmrgstatic ColorRegister null_color =
160f2e35a3aSmrg{-1, -1, -1};
161894e0ac8Smrg
162894e0ac8Smrgstatic ColorRegister *
163894e0ac8SmrgallocRegisters(void)
164e0a2b6dfSmrg{
165894e0ac8Smrg    return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
166894e0ac8Smrg}
167894e0ac8Smrg
168894e0ac8Smrgstatic Graphic *
169894e0ac8SmrgfreeGraphic(Graphic *obj)
170894e0ac8Smrg{
171894e0ac8Smrg    if (obj) {
172f2e35a3aSmrg	free(obj->pixels);
173f2e35a3aSmrg	free(obj->private_color_registers);
174894e0ac8Smrg	free(obj);
175894e0ac8Smrg    }
176894e0ac8Smrg    return NULL;
177894e0ac8Smrg}
178894e0ac8Smrg
179894e0ac8Smrgstatic Graphic *
18001037d57SmrgallocGraphic(int max_w, int max_h)
181894e0ac8Smrg{
182894e0ac8Smrg    Graphic *result = TypeCalloc(Graphic);
183894e0ac8Smrg    if (result) {
18401037d57Smrg	result->max_width = max_w;
18501037d57Smrg	result->max_height = max_h;
18601037d57Smrg	if (!(result->pixels = TypeCallocN(RegisterNum,
18701037d57Smrg					     (size_t) max_w * (size_t) max_h))) {
188894e0ac8Smrg	    result = freeGraphic(result);
189894e0ac8Smrg	} else if (!(result->private_color_registers = allocRegisters())) {
190894e0ac8Smrg	    result = freeGraphic(result);
191e0a2b6dfSmrg	}
192e0a2b6dfSmrg    }
193894e0ac8Smrg    return result;
194894e0ac8Smrg}
195e0a2b6dfSmrg
196f2e35a3aSmrg#define getActiveSlot(n) \
197f2e35a3aSmrg	(((n) < MAX_GRAPHICS && \
198f2e35a3aSmrg	 displayed_graphics[n] && \
199f2e35a3aSmrg	 displayed_graphics[n]->valid) \
200f2e35a3aSmrg	 ? displayed_graphics[n] \
201f2e35a3aSmrg	 : NULL)
202e0a2b6dfSmrg
203894e0ac8Smrgstatic Graphic *
20401037d57SmrggetInactiveSlot(const TScreen *screen, unsigned n)
205894e0ac8Smrg{
206894e0ac8Smrg    if (n < MAX_GRAPHICS &&
207894e0ac8Smrg	(!displayed_graphics[n] ||
208894e0ac8Smrg	 !displayed_graphics[n]->valid)) {
209894e0ac8Smrg	if (!displayed_graphics[n]) {
21001037d57Smrg	    displayed_graphics[n] = allocGraphic(screen->graphics_max_wide,
21101037d57Smrg						 screen->graphics_max_high);
212f2e35a3aSmrg	    used_graphics += (displayed_graphics[n] != NULL);
213894e0ac8Smrg	}
214894e0ac8Smrg	return displayed_graphics[n];
215894e0ac8Smrg    }
216894e0ac8Smrg    return NULL;
217894e0ac8Smrg}
218894e0ac8Smrg
219894e0ac8Smrgstatic ColorRegister *
220894e0ac8SmrggetSharedRegisters(void)
221894e0ac8Smrg{
222894e0ac8Smrg    if (!shared_color_registers)
223894e0ac8Smrg	shared_color_registers = allocRegisters();
224894e0ac8Smrg    return shared_color_registers;
225894e0ac8Smrg}
226e0a2b6dfSmrg
227e0a2b6dfSmrgstatic void
228894e0ac8SmrgdeactivateSlot(unsigned n)
229e0a2b6dfSmrg{
230f2e35a3aSmrg    if ((n < MAX_GRAPHICS) && displayed_graphics[n]) {
231894e0ac8Smrg	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
232f2e35a3aSmrg	used_graphics--;
233894e0ac8Smrg    }
234894e0ac8Smrg}
235e0a2b6dfSmrg
236894e0ac8Smrgextern RegisterNum
237894e0ac8Smrgread_pixel(Graphic *graphic, int x, int y)
238894e0ac8Smrg{
23901037d57Smrg    if (x < 0 || x >= graphic->actual_width ||
24001037d57Smrg	y < 0 || y >= graphic->actual_height) {
241894e0ac8Smrg	return COLOR_HOLE;
242e0a2b6dfSmrg    }
243894e0ac8Smrg
244894e0ac8Smrg    return graphic->pixels[y * graphic->max_width + x];
245e0a2b6dfSmrg}
246e0a2b6dfSmrg
24701037d57Smrg#define _draw_pixel(G, X, Y, C) \
24801037d57Smrg    do { \
24901037d57Smrg        (G)->pixels[(Y) * (G)->max_width + (X)] = (RegisterNum) (C); \
25001037d57Smrg    } while (0)
25101037d57Smrg
252894e0ac8Smrgvoid
253894e0ac8Smrgdraw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
254e0a2b6dfSmrg{
255894e0ac8Smrg    assert(color <= MAX_COLOR_REGISTERS);
256e0a2b6dfSmrg
257894e0ac8Smrg#ifdef DEBUG_PIXEL
258894e0ac8Smrg    TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
259894e0ac8Smrg	   x,
260894e0ac8Smrg	   y,
261e0a2b6dfSmrg	   color,
262894e0ac8Smrg	   COLOR_HOLE,
263e0a2b6dfSmrg	   ((color != COLOR_HOLE)
264894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].r : 0U),
265e0a2b6dfSmrg	   ((color != COLOR_HOLE)
266894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].g : 0U),
267e0a2b6dfSmrg	   ((color != COLOR_HOLE)
268894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].b : 0U)));
269894e0ac8Smrg#endif
270894e0ac8Smrg    if (x >= 0 && x < graphic->actual_width &&
271894e0ac8Smrg	y >= 0 && y < graphic->actual_height) {
27201037d57Smrg	_draw_pixel(graphic, x, y, color);
273894e0ac8Smrg	if (color < MAX_COLOR_REGISTERS)
274894e0ac8Smrg	    graphic->color_registers_used[color] = 1;
275894e0ac8Smrg    }
276894e0ac8Smrg}
277894e0ac8Smrg
278894e0ac8Smrgvoid
279894e0ac8Smrgdraw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
280894e0ac8Smrg{
281894e0ac8Smrg    int x, y;
282894e0ac8Smrg    int tmp;
283894e0ac8Smrg
284894e0ac8Smrg    assert(color <= MAX_COLOR_REGISTERS);
285894e0ac8Smrg
286894e0ac8Smrg    if (x1 > x2) {
287894e0ac8Smrg	EXCHANGE(x1, x2, tmp);
288894e0ac8Smrg    }
289894e0ac8Smrg    if (y1 > y2) {
290894e0ac8Smrg	EXCHANGE(y1, y2, tmp);
291894e0ac8Smrg    }
292894e0ac8Smrg
29301037d57Smrg    if (x2 < 0 || x1 >= graphic->actual_width ||
29401037d57Smrg	y2 < 0 || y1 >= graphic->actual_height)
29501037d57Smrg	return;
29601037d57Smrg
29701037d57Smrg    if (x1 < 0)
29801037d57Smrg	x1 = 0;
29901037d57Smrg    if (x2 >= graphic->actual_width)
30001037d57Smrg	x2 = graphic->actual_width - 1;
30101037d57Smrg    if (y1 < 0)
30201037d57Smrg	y1 = 0;
30301037d57Smrg    if (y2 >= graphic->actual_height)
30401037d57Smrg	y2 = graphic->actual_height - 1;
30501037d57Smrg
30601037d57Smrg    if (color < MAX_COLOR_REGISTERS)
30701037d57Smrg	graphic->color_registers_used[color] = 1;
308894e0ac8Smrg    for (y = y1; y <= y2; y++)
30901037d57Smrg	for (x = x1; x <= x2; x++)
31001037d57Smrg	    _draw_pixel(graphic, x, y, color);
311894e0ac8Smrg}
312894e0ac8Smrg
313f2e35a3aSmrg#if 0				/* unused */
314894e0ac8Smrgvoid
315894e0ac8Smrgdraw_solid_line(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
316894e0ac8Smrg{
317894e0ac8Smrg    int x, y;
318894e0ac8Smrg    int dx, dy;
319894e0ac8Smrg    int dir, diff;
320894e0ac8Smrg
321894e0ac8Smrg    assert(color <= MAX_COLOR_REGISTERS);
322894e0ac8Smrg
323894e0ac8Smrg    dx = abs(x1 - x2);
324894e0ac8Smrg    dy = abs(y1 - y2);
325894e0ac8Smrg
326894e0ac8Smrg    if (dx > dy) {
327894e0ac8Smrg	if (x1 > x2) {
328894e0ac8Smrg	    int tmp;
329894e0ac8Smrg	    EXCHANGE(x1, x2, tmp);
330894e0ac8Smrg	    EXCHANGE(y1, y2, tmp);
331894e0ac8Smrg	}
332894e0ac8Smrg	if (y1 < y2)
333894e0ac8Smrg	    dir = 1;
334894e0ac8Smrg	else if (y1 > y2)
335894e0ac8Smrg	    dir = -1;
336894e0ac8Smrg	else
337894e0ac8Smrg	    dir = 0;
338894e0ac8Smrg
339894e0ac8Smrg	diff = 0;
340894e0ac8Smrg	y = y1;
341894e0ac8Smrg	for (x = x1; x <= x2; x++) {
342894e0ac8Smrg	    if (diff >= dx) {
343894e0ac8Smrg		diff -= dx;
344894e0ac8Smrg		y += dir;
345e0a2b6dfSmrg	    }
346894e0ac8Smrg	    diff += dy;
347894e0ac8Smrg	    draw_solid_pixel(graphic, x, y, color);
348894e0ac8Smrg	}
349894e0ac8Smrg    } else {
350894e0ac8Smrg	if (y1 > y2) {
351894e0ac8Smrg	    int tmp;
352894e0ac8Smrg	    EXCHANGE(x1, x2, tmp);
353894e0ac8Smrg	    EXCHANGE(y1, y2, tmp);
354894e0ac8Smrg	}
355894e0ac8Smrg	if (x1 < x2)
356894e0ac8Smrg	    dir = 1;
357894e0ac8Smrg	else if (x1 > x2)
358894e0ac8Smrg	    dir = -1;
359894e0ac8Smrg	else
360894e0ac8Smrg	    dir = 0;
361894e0ac8Smrg
362894e0ac8Smrg	diff = 0;
363894e0ac8Smrg	x = x1;
364894e0ac8Smrg	for (y = y1; y <= y2; y++) {
365894e0ac8Smrg	    if (diff >= dy) {
366894e0ac8Smrg		diff -= dy;
367894e0ac8Smrg		x += dir;
368894e0ac8Smrg	    }
369894e0ac8Smrg	    diff += dx;
370894e0ac8Smrg	    draw_solid_pixel(graphic, x, y, color);
371e0a2b6dfSmrg	}
372e0a2b6dfSmrg    }
373e0a2b6dfSmrg}
374f2e35a3aSmrg#endif
375e0a2b6dfSmrg
37601037d57Smrgvoid
37701037d57Smrgcopy_overlapping_area(Graphic *graphic, int src_ul_x, int src_ul_y,
37801037d57Smrg		      int dst_ul_x, int dst_ul_y, unsigned w, unsigned h,
37901037d57Smrg		      unsigned default_color)
38001037d57Smrg{
38101037d57Smrg    int sx, ex, dx;
38201037d57Smrg    int sy, ey, dy;
38301037d57Smrg    int xx, yy;
38401037d57Smrg    RegisterNum color;
38501037d57Smrg
38601037d57Smrg    if (dst_ul_x <= src_ul_x) {
38701037d57Smrg	sx = 0;
38801037d57Smrg	ex = (int) w - 1;
38901037d57Smrg	dx = +1;
39001037d57Smrg    } else {
39101037d57Smrg	sx = (int) w - 1;
39201037d57Smrg	ex = 0;
39301037d57Smrg	dx = -1;
39401037d57Smrg    }
39501037d57Smrg
39601037d57Smrg    if (dst_ul_y <= src_ul_y) {
39701037d57Smrg	sy = 0;
39801037d57Smrg	ey = (int) h - 1;
39901037d57Smrg	dy = +1;
40001037d57Smrg    } else {
40101037d57Smrg	sy = (int) h - 1;
40201037d57Smrg	ey = 0;
40301037d57Smrg	dy = -1;
40401037d57Smrg    }
40501037d57Smrg
40601037d57Smrg    for (yy = sy; yy != ey + dy; yy += dy) {
4072e4f8982Smrg	int dst_y = dst_ul_y + yy;
4082e4f8982Smrg	int src_y = src_ul_y + yy;
40901037d57Smrg	if (dst_y < 0 || dst_y >= (int) graphic->actual_height)
41001037d57Smrg	    continue;
41101037d57Smrg
41201037d57Smrg	for (xx = sx; xx != ex + dx; xx += dx) {
4132e4f8982Smrg	    int dst_x = dst_ul_x + xx;
4142e4f8982Smrg	    int src_x = src_ul_x + xx;
41501037d57Smrg	    if (dst_x < 0 || dst_x >= (int) graphic->actual_width)
41601037d57Smrg		continue;
41701037d57Smrg
41801037d57Smrg	    if (src_x < 0 || src_x >= (int) graphic->actual_width ||
41901037d57Smrg		src_y < 0 || src_y >= (int) graphic->actual_height)
42001037d57Smrg		color = (RegisterNum) default_color;
42101037d57Smrg	    else
42201037d57Smrg		color = graphic->pixels[(unsigned) (src_y *
42301037d57Smrg						    graphic->max_width) +
42401037d57Smrg					(unsigned) src_x];
42501037d57Smrg
42601037d57Smrg	    graphic->pixels[(unsigned) (dst_y * graphic->max_width) +
42701037d57Smrg			    (unsigned) dst_x] = color;
42801037d57Smrg	}
42901037d57Smrg    }
43001037d57Smrg}
43101037d57Smrg
432e0a2b6dfSmrgstatic void
433894e0ac8Smrgset_color_register(ColorRegister *color_registers,
434894e0ac8Smrg		   unsigned color,
435894e0ac8Smrg		   int r,
436894e0ac8Smrg		   int g,
437894e0ac8Smrg		   int b)
438e0a2b6dfSmrg{
439e0a2b6dfSmrg    ColorRegister *reg = &color_registers[color];
440e0a2b6dfSmrg    reg->r = (short) r;
441e0a2b6dfSmrg    reg->g = (short) g;
442e0a2b6dfSmrg    reg->b = (short) b;
443e0a2b6dfSmrg}
444e0a2b6dfSmrg
445894e0ac8Smrg/* Graphics which don't use private colors will act as if they are using a
446894e0ac8Smrg * device-wide color palette.
447894e0ac8Smrg */
448894e0ac8Smrgstatic void
449894e0ac8Smrgset_shared_color_register(unsigned color, int r, int g, int b)
450894e0ac8Smrg{
451894e0ac8Smrg    unsigned ii;
452894e0ac8Smrg
453894e0ac8Smrg    assert(color < MAX_COLOR_REGISTERS);
454894e0ac8Smrg
455894e0ac8Smrg    set_color_register(getSharedRegisters(), color, r, g, b);
456894e0ac8Smrg
457f2e35a3aSmrg    if (!used_graphics)
458f2e35a3aSmrg	return;
459f2e35a3aSmrg
460894e0ac8Smrg    FOR_EACH_SLOT(ii) {
4612e4f8982Smrg	Graphic *graphic;
4622e4f8982Smrg
463894e0ac8Smrg	if (!(graphic = getActiveSlot(ii)))
464894e0ac8Smrg	    continue;
465894e0ac8Smrg	if (graphic->private_colors)
466894e0ac8Smrg	    continue;
467894e0ac8Smrg
468894e0ac8Smrg	if (graphic->color_registers_used[ii]) {
469894e0ac8Smrg	    graphic->dirty = 1;
470894e0ac8Smrg	}
471894e0ac8Smrg    }
472894e0ac8Smrg}
473894e0ac8Smrg
474894e0ac8Smrgvoid
475894e0ac8Smrgupdate_color_register(Graphic *graphic,
476894e0ac8Smrg		      unsigned color,
477894e0ac8Smrg		      int r,
478894e0ac8Smrg		      int g,
479894e0ac8Smrg		      int b)
480894e0ac8Smrg{
481894e0ac8Smrg    assert(color < MAX_COLOR_REGISTERS);
482894e0ac8Smrg
483894e0ac8Smrg    if (graphic->private_colors) {
484894e0ac8Smrg	set_color_register(graphic->private_color_registers,
485894e0ac8Smrg			   color, r, g, b);
486894e0ac8Smrg	if (graphic->color_registers_used[color]) {
487894e0ac8Smrg	    graphic->dirty = 1;
488894e0ac8Smrg	}
489894e0ac8Smrg	graphic->color_registers_used[color] = 1;
490894e0ac8Smrg    } else {
491894e0ac8Smrg	set_shared_color_register(color, r, g, b);
492894e0ac8Smrg    }
493894e0ac8Smrg}
494894e0ac8Smrg
495894e0ac8Smrg#define SQUARE(X) ( (X) * (X) )
496894e0ac8Smrg
497894e0ac8SmrgRegisterNum
498894e0ac8Smrgfind_color_register(ColorRegister const *color_registers, int r, int g, int b)
499894e0ac8Smrg{
500894e0ac8Smrg    unsigned i;
501894e0ac8Smrg    unsigned closest_index;
502894e0ac8Smrg    unsigned closest_distance;
503894e0ac8Smrg
504894e0ac8Smrg    /* I have no idea what algorithm DEC used for this.
505894e0ac8Smrg     * The documentation warns that it is unpredictable, especially with values
506894e0ac8Smrg     * far away from any allocated color so it is probably a very simple
50701037d57Smrg     * heuristic rather than something fancy like finding the minimum distance
508894e0ac8Smrg     * in a linear perceptive color space.
509894e0ac8Smrg     */
510894e0ac8Smrg    closest_index = MAX_COLOR_REGISTERS;
511894e0ac8Smrg    closest_distance = 0U;
512894e0ac8Smrg    for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
5132e4f8982Smrg	unsigned d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
5142e4f8982Smrg				 SQUARE(3 * (color_registers[i].g - g)) +
5152e4f8982Smrg				 SQUARE(1 * (color_registers[i].b - b)));
516894e0ac8Smrg	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
517894e0ac8Smrg	    closest_index = i;
518894e0ac8Smrg	    closest_distance = d;
519894e0ac8Smrg	}
520894e0ac8Smrg    }
521894e0ac8Smrg
522894e0ac8Smrg    TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
523894e0ac8Smrg	   r, g, b,
524894e0ac8Smrg	   closest_index,
525894e0ac8Smrg	   closest_distance,
526894e0ac8Smrg	   color_registers[closest_index].r,
527894e0ac8Smrg	   color_registers[closest_index].g,
528894e0ac8Smrg	   color_registers[closest_index].b));
529894e0ac8Smrg    return (RegisterNum) closest_index;
530894e0ac8Smrg}
531894e0ac8Smrg
532e0a2b6dfSmrgstatic void
533f2e35a3aSmrginit_color_registers(ColorRegister *color_registers, int graphics_termid)
534e0a2b6dfSmrg{
535f2e35a3aSmrg    TRACE(("setting initial colors for terminal %d\n", graphics_termid));
536e0a2b6dfSmrg    {
537894e0ac8Smrg	unsigned i;
538e0a2b6dfSmrg
539e0a2b6dfSmrg	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
540894e0ac8Smrg	    set_color_register(color_registers, (RegisterNum) i, 0, 0, 0);
541e0a2b6dfSmrg	}
542e0a2b6dfSmrg    }
543e0a2b6dfSmrg
544e0a2b6dfSmrg    /*
545e0a2b6dfSmrg     * default color registers:
546e0a2b6dfSmrg     *     (mono) (color)
547e0a2b6dfSmrg     * VK100/GIGI (fixed)
548e0a2b6dfSmrg     * VT125:
549e0a2b6dfSmrg     *   0: 0%      0%
550e0a2b6dfSmrg     *   1: 33%     blue
551e0a2b6dfSmrg     *   2: 66%     red
552e0a2b6dfSmrg     *   3: 100%    green
553e0a2b6dfSmrg     * VT240:
554e0a2b6dfSmrg     *   0: 0%      0%
555e0a2b6dfSmrg     *   1: 33%     blue
556e0a2b6dfSmrg     *   2: 66%     red
557e0a2b6dfSmrg     *   3: 100%    green
558e0a2b6dfSmrg     * VT241:
559e0a2b6dfSmrg     *   0: 0%      0%
560e0a2b6dfSmrg     *   1: 33%     blue
561e0a2b6dfSmrg     *   2: 66%     red
562e0a2b6dfSmrg     *   3: 100%    green
563e0a2b6dfSmrg     * VT330:
564e0a2b6dfSmrg     *   0: 0%      0%              (bg for light on dark mode)
565e0a2b6dfSmrg     *   1: 33%     blue (red?)
566e0a2b6dfSmrg     *   2: 66%     red (green?)
567e0a2b6dfSmrg     *   3: 100%    green (yellow?) (fg for light on dark mode)
568e0a2b6dfSmrg     * VT340:
569e0a2b6dfSmrg     *   0: 0%      0%              (bg for light on dark mode)
570e0a2b6dfSmrg     *   1: 14%     blue
571e0a2b6dfSmrg     *   2: 29%     red
572e0a2b6dfSmrg     *   3: 43%     green
573e0a2b6dfSmrg     *   4: 57%     magenta
574e0a2b6dfSmrg     *   5: 71%     cyan
575e0a2b6dfSmrg     *   6: 86%     yellow
576e0a2b6dfSmrg     *   7: 100%    50%             (fg for light on dark mode)
577e0a2b6dfSmrg     *   8: 0%      25%
578e0a2b6dfSmrg     *   9: 14%     gray-blue
579e0a2b6dfSmrg     *  10: 29%     gray-red
580e0a2b6dfSmrg     *  11: 43%     gray-green
581e0a2b6dfSmrg     *  12: 57%     gray-magenta
582e0a2b6dfSmrg     *  13: 71%     gray-cyan
583e0a2b6dfSmrg     *  14: 86%     gray-yellow
584894e0ac8Smrg     *  15: 100%    75%             ("white")
585894e0ac8Smrg     * VT382:
586894e0ac8Smrg     *   ? (FIXME: B&W only?)
587e0a2b6dfSmrg     * dxterm:
588e0a2b6dfSmrg     *  ?
589e0a2b6dfSmrg     */
590f2e35a3aSmrg    switch (graphics_termid) {
591e0a2b6dfSmrg    case 125:
592e0a2b6dfSmrg    case 241:
593894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
594894e0ac8Smrg	set_color_register(color_registers, 1, 0, 0, 100);
595894e0ac8Smrg	set_color_register(color_registers, 2, 0, 100, 0);
596894e0ac8Smrg	set_color_register(color_registers, 3, 100, 0, 0);
597e0a2b6dfSmrg	break;
598e0a2b6dfSmrg    case 240:
599e0a2b6dfSmrg    case 330:
600894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
601894e0ac8Smrg	set_color_register(color_registers, 1, 33, 33, 33);
602894e0ac8Smrg	set_color_register(color_registers, 2, 66, 66, 66);
603894e0ac8Smrg	set_color_register(color_registers, 3, 100, 100, 100);
604e0a2b6dfSmrg	break;
605e0a2b6dfSmrg    case 340:
606e0a2b6dfSmrg    default:
607894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
608894e0ac8Smrg	set_color_register(color_registers, 1, 20, 20, 80);
609894e0ac8Smrg	set_color_register(color_registers, 2, 80, 13, 13);
610894e0ac8Smrg	set_color_register(color_registers, 3, 20, 80, 20);
611894e0ac8Smrg	set_color_register(color_registers, 4, 80, 20, 80);
612894e0ac8Smrg	set_color_register(color_registers, 5, 20, 80, 80);
613894e0ac8Smrg	set_color_register(color_registers, 6, 80, 80, 20);
614894e0ac8Smrg	set_color_register(color_registers, 7, 53, 53, 53);
615894e0ac8Smrg	set_color_register(color_registers, 8, 26, 26, 26);
616894e0ac8Smrg	set_color_register(color_registers, 9, 33, 33, 60);
617894e0ac8Smrg	set_color_register(color_registers, 10, 60, 26, 26);
618894e0ac8Smrg	set_color_register(color_registers, 11, 33, 60, 33);
619894e0ac8Smrg	set_color_register(color_registers, 12, 60, 33, 60);
620894e0ac8Smrg	set_color_register(color_registers, 13, 33, 60, 60);
621894e0ac8Smrg	set_color_register(color_registers, 14, 60, 60, 33);
622894e0ac8Smrg	set_color_register(color_registers, 15, 80, 80, 80);
623894e0ac8Smrg	break;
624894e0ac8Smrg    case 382:			/* FIXME: verify */
625894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
626894e0ac8Smrg	set_color_register(color_registers, 1, 100, 100, 100);
627e0a2b6dfSmrg	break;
628e0a2b6dfSmrg    }
629e0a2b6dfSmrg
630894e0ac8Smrg#ifdef DEBUG_PALETTE
631894e0ac8Smrg    {
632894e0ac8Smrg	unsigned i;
633e0a2b6dfSmrg
634894e0ac8Smrg	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
63501037d57Smrg	    TRACE(("initial value for register %03u: %d,%d,%d\n",
636894e0ac8Smrg		   i,
637894e0ac8Smrg		   color_registers[i].r,
638894e0ac8Smrg		   color_registers[i].g,
63901037d57Smrg		   color_registers[i].b));
640894e0ac8Smrg	}
641894e0ac8Smrg    }
642894e0ac8Smrg#endif
643894e0ac8Smrg}
644e0a2b6dfSmrg
645894e0ac8Smrgunsigned
646894e0ac8Smrgget_color_register_count(TScreen const *screen)
647894e0ac8Smrg{
648894e0ac8Smrg    unsigned num_color_registers;
649e0a2b6dfSmrg
650894e0ac8Smrg    if (screen->numcolorregisters >= 0) {
651894e0ac8Smrg	num_color_registers = (unsigned) screen->numcolorregisters;
652894e0ac8Smrg    } else {
653894e0ac8Smrg	num_color_registers = 0U;
654894e0ac8Smrg    }
655e0a2b6dfSmrg
656894e0ac8Smrg    if (num_color_registers > 1U) {
657894e0ac8Smrg	if (num_color_registers > MAX_COLOR_REGISTERS)
658894e0ac8Smrg	    return MAX_COLOR_REGISTERS;
659894e0ac8Smrg	return num_color_registers;
660894e0ac8Smrg    }
661e0a2b6dfSmrg
662e0a2b6dfSmrg    /*
663e0a2b6dfSmrg     * color capabilities:
664e0a2b6dfSmrg     * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
665e0a2b6dfSmrg     * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
666894e0ac8Smrg     * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
667e0a2b6dfSmrg     * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
668e0a2b6dfSmrg     * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
669e0a2b6dfSmrg     * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
670894e0ac8Smrg     * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
671e0a2b6dfSmrg     * dxterm      ?
672e0a2b6dfSmrg     */
673f2e35a3aSmrg    switch (screen->graphics_termid) {
674e0a2b6dfSmrg    case 125:
675894e0ac8Smrg	return 4U;
676e0a2b6dfSmrg    case 240:
677894e0ac8Smrg	return 4U;
678e0a2b6dfSmrg    case 241:
679894e0ac8Smrg	return 4U;
680e0a2b6dfSmrg    case 330:
681894e0ac8Smrg	return 4U;
682e0a2b6dfSmrg    case 340:
683894e0ac8Smrg	return 16U;
684894e0ac8Smrg    case 382:
685894e0ac8Smrg	return 2U;
686e0a2b6dfSmrg    default:
687894e0ac8Smrg	/* unknown graphics model -- might as well be generous */
688894e0ac8Smrg	return MAX_COLOR_REGISTERS;
689e0a2b6dfSmrg    }
690894e0ac8Smrg}
691894e0ac8Smrg
692894e0ac8Smrgstatic void
693894e0ac8Smrginit_graphic(Graphic *graphic,
694894e0ac8Smrg	     unsigned type,
695f2e35a3aSmrg	     int graphics_termid,
696894e0ac8Smrg	     int charrow,
697894e0ac8Smrg	     int charcol,
698894e0ac8Smrg	     unsigned num_color_registers,
699894e0ac8Smrg	     int private_colors)
700894e0ac8Smrg{
70101037d57Smrg    const unsigned max_pixels = (unsigned) (graphic->max_width *
70201037d57Smrg					    graphic->max_height);
703894e0ac8Smrg    unsigned i;
704894e0ac8Smrg
705f2e35a3aSmrg    TRACE(("init_graphic at %d,%d\n", charrow, charcol));
706894e0ac8Smrg
7072e4f8982Smrg    graphic->hidden = 0;
708894e0ac8Smrg    graphic->dirty = 1;
70901037d57Smrg    for (i = 0U; i < max_pixels; i++)
710894e0ac8Smrg	graphic->pixels[i] = COLOR_HOLE;
711894e0ac8Smrg    memset(graphic->color_registers_used, 0, sizeof(graphic->color_registers_used));
712e0a2b6dfSmrg
713e0a2b6dfSmrg    /*
714e0a2b6dfSmrg     * text and graphics interactions:
715e0a2b6dfSmrg     * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
716e0a2b6dfSmrg     * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
717894e0ac8Smrg     * VT382                     text writes on top of graphics buffer FIXME: verify
718e0a2b6dfSmrg     * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
719e0a2b6dfSmrg     */
720e0a2b6dfSmrg
721894e0ac8Smrg    /*
722894e0ac8Smrg     * dimensions (ReGIS logical, physical):
723894e0ac8Smrg     * VK100/GIGI  768x4??  768x240(status?)
724894e0ac8Smrg     * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
725894e0ac8Smrg     * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
726894e0ac8Smrg     * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
727894e0ac8Smrg     * VT330       800x480  800x480(+?status)
728894e0ac8Smrg     * VT340       800x480  800x480(+?status)
729894e0ac8Smrg     * VT382       960x750  sixel only
730894e0ac8Smrg     * dxterm      ?x? ?x?  variable?
731894e0ac8Smrg     */
732e0a2b6dfSmrg
733e0a2b6dfSmrg    graphic->actual_width = 0;
734e0a2b6dfSmrg    graphic->actual_height = 0;
735e0a2b6dfSmrg
736894e0ac8Smrg    graphic->pixw = 1;
737894e0ac8Smrg    graphic->pixh = 1;
738894e0ac8Smrg
739894e0ac8Smrg    graphic->valid_registers = num_color_registers;
740894e0ac8Smrg    TRACE(("%d color registers\n", graphic->valid_registers));
741894e0ac8Smrg
742e0a2b6dfSmrg    graphic->private_colors = private_colors;
743e0a2b6dfSmrg    if (graphic->private_colors) {
744894e0ac8Smrg	TRACE(("using private color registers\n"));
745f2e35a3aSmrg	init_color_registers(graphic->private_color_registers, graphics_termid);
746e0a2b6dfSmrg	graphic->color_registers = graphic->private_color_registers;
747e0a2b6dfSmrg    } else {
748894e0ac8Smrg	TRACE(("using shared color registers\n"));
749894e0ac8Smrg	graphic->color_registers = getSharedRegisters();
750e0a2b6dfSmrg    }
751e0a2b6dfSmrg
752894e0ac8Smrg    graphic->charrow = charrow;
753894e0ac8Smrg    graphic->charcol = charcol;
754894e0ac8Smrg    graphic->type = type;
755e0a2b6dfSmrg    graphic->valid = 0;
756e0a2b6dfSmrg}
757e0a2b6dfSmrg
758894e0ac8SmrgGraphic *
759894e0ac8Smrgget_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
760e0a2b6dfSmrg{
761e0a2b6dfSmrg    TScreen const *screen = TScreenOf(xw);
762e0a2b6dfSmrg    int bufferid = screen->whichBuf;
763f2e35a3aSmrg    int graphics_termid = GraphicsTermId(screen);
764f2e35a3aSmrg    Graphic *graphic = NULL;
765894e0ac8Smrg    unsigned ii;
766e0a2b6dfSmrg
767894e0ac8Smrg    FOR_EACH_SLOT(ii) {
76801037d57Smrg	if ((graphic = getInactiveSlot(screen, ii))) {
769894e0ac8Smrg	    TRACE(("using fresh graphic index=%u id=%u\n", ii, next_graphic_id));
770e0a2b6dfSmrg	    break;
771894e0ac8Smrg	}
772e0a2b6dfSmrg    }
773e0a2b6dfSmrg
774894e0ac8Smrg    /* if none are free, recycle the graphic scrolled back the farthest */
775894e0ac8Smrg    if (!graphic) {
776e0a2b6dfSmrg	int min_charrow = 0;
777894e0ac8Smrg	Graphic *min_graphic = NULL;
778e0a2b6dfSmrg
779894e0ac8Smrg	FOR_EACH_SLOT(ii) {
780894e0ac8Smrg	    if (!(graphic = getActiveSlot(ii)))
781894e0ac8Smrg		continue;
782e0a2b6dfSmrg	    if (!min_graphic || graphic->charrow < min_charrow) {
783e0a2b6dfSmrg		min_charrow = graphic->charrow;
784e0a2b6dfSmrg		min_graphic = graphic;
785e0a2b6dfSmrg	    }
786e0a2b6dfSmrg	}
787894e0ac8Smrg	TRACE(("recycling old graphic index=%u id=%u\n", ii, next_graphic_id));
788e0a2b6dfSmrg	graphic = min_graphic;
789e0a2b6dfSmrg    }
790e0a2b6dfSmrg
791894e0ac8Smrg    if (graphic) {
792894e0ac8Smrg	unsigned num_color_registers;
793894e0ac8Smrg	num_color_registers = get_color_register_count(screen);
794894e0ac8Smrg	graphic->xw = xw;
795894e0ac8Smrg	graphic->bufferid = bufferid;
796894e0ac8Smrg	graphic->id = next_graphic_id++;
797894e0ac8Smrg	init_graphic(graphic,
798894e0ac8Smrg		     type,
799f2e35a3aSmrg		     graphics_termid,
800894e0ac8Smrg		     charrow,
801894e0ac8Smrg		     charcol,
802894e0ac8Smrg		     num_color_registers,
803894e0ac8Smrg		     screen->privatecolorregisters);
804894e0ac8Smrg    }
805e0a2b6dfSmrg    return graphic;
806e0a2b6dfSmrg}
807e0a2b6dfSmrg
808894e0ac8SmrgGraphic *
809894e0ac8Smrgget_new_or_matching_graphic(XtermWidget xw,
810894e0ac8Smrg			    int charrow,
811894e0ac8Smrg			    int charcol,
812894e0ac8Smrg			    int actual_width,
813894e0ac8Smrg			    int actual_height,
814894e0ac8Smrg			    unsigned type)
815e0a2b6dfSmrg{
816894e0ac8Smrg    TScreen const *screen = TScreenOf(xw);
817894e0ac8Smrg    int bufferid = screen->whichBuf;
818894e0ac8Smrg    Graphic *graphic;
819894e0ac8Smrg    unsigned ii;
820894e0ac8Smrg
821894e0ac8Smrg    FOR_EACH_SLOT(ii) {
8222e4f8982Smrg	TRACE(("checking slot=%u for graphic at %d,%d %dx%d bufferid=%d type=%u\n", ii,
8232e4f8982Smrg	       charrow, charcol,
8242e4f8982Smrg	       actual_width, actual_height,
8252e4f8982Smrg	       bufferid, type));
8262e4f8982Smrg	if ((graphic = getActiveSlot(ii))) {
8272e4f8982Smrg	    if (graphic->type == type &&
8282e4f8982Smrg		graphic->bufferid == bufferid &&
8292e4f8982Smrg		graphic->charrow == charrow &&
8302e4f8982Smrg		graphic->charcol == charcol &&
8312e4f8982Smrg		graphic->actual_width == actual_width &&
8322e4f8982Smrg		graphic->actual_height == actual_height) {
8332e4f8982Smrg		TRACE(("found existing graphic slot=%u id=%u\n", ii, graphic->id));
8342e4f8982Smrg		return graphic;
8352e4f8982Smrg	    }
8362e4f8982Smrg	    TRACE(("not a match: graphic at %d,%d %dx%d bufferid=%d type=%u\n",
8372e4f8982Smrg		   graphic->charrow, graphic->charcol,
8382e4f8982Smrg		   graphic->actual_width, graphic->actual_height,
8392e4f8982Smrg		   graphic->bufferid, graphic->type));
840e0a2b6dfSmrg	}
841e0a2b6dfSmrg    }
842e0a2b6dfSmrg
843894e0ac8Smrg    /* if no match get a new graphic */
844894e0ac8Smrg    if ((graphic = get_new_graphic(xw, charrow, charcol, type))) {
845894e0ac8Smrg	graphic->actual_width = actual_width;
846894e0ac8Smrg	graphic->actual_height = actual_height;
8472e4f8982Smrg	TRACE(("no match; created graphic at %d,%d %dx%d bufferid=%d type=%u\n",
8482e4f8982Smrg	       graphic->charrow, graphic->charcol,
8492e4f8982Smrg	       graphic->actual_width, graphic->actual_height,
8502e4f8982Smrg	       graphic->bufferid, graphic->type));
851e0a2b6dfSmrg    }
852894e0ac8Smrg    return graphic;
853e0a2b6dfSmrg}
854e0a2b6dfSmrg
855a5ae21e4Smrg#define ScaleForXColor(s) (unsigned short) ((unsigned long)(s) * MAX_U_COLOR / CHANNEL_MAX)
85601037d57Smrg
85701037d57Smrgstatic int
85801037d57Smrgsave_allocated_color(const ColorRegister *reg, XtermWidget xw, Pixel *pix)
85901037d57Smrg{
86001037d57Smrg    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
86101037d57Smrg    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
86201037d57Smrg    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
86301037d57Smrg    XColor xcolor;
86401037d57Smrg    AllocatedColorRegister *new_color;
86501037d57Smrg
86601037d57Smrg    xcolor.pixel = 0UL;
86701037d57Smrg    xcolor.red = ScaleForXColor(reg->r);
86801037d57Smrg    xcolor.green = ScaleForXColor(reg->g);
86901037d57Smrg    xcolor.blue = ScaleForXColor(reg->b);
87001037d57Smrg    xcolor.flags = DoRed | DoGreen | DoBlue;
87101037d57Smrg    if (!allocateBestRGB(xw, &xcolor)) {
87201037d57Smrg	TRACE(("unable to allocate xcolor\n"));
87301037d57Smrg	*pix = 0UL;
87401037d57Smrg	return 0;
875f2e35a3aSmrg    } else {
876f2e35a3aSmrg	*pix = xcolor.pixel;
87701037d57Smrg
878f2e35a3aSmrg	if (!(new_color = malloc(sizeof(*new_color)))) {
879f2e35a3aSmrg	    TRACE(("unable to save pixel %lu\n", (unsigned long) *pix));
880f2e35a3aSmrg	    return 0;
881f2e35a3aSmrg	} else {
882f2e35a3aSmrg	    new_color->r = reg->r;
883f2e35a3aSmrg	    new_color->g = reg->g;
884f2e35a3aSmrg	    new_color->b = reg->b;
885f2e35a3aSmrg	    new_color->pix = *pix;
886f2e35a3aSmrg	    new_color->next = allocated_colors[rr][gg][bb];
88701037d57Smrg
888f2e35a3aSmrg	    allocated_colors[rr][gg][bb] = new_color;
88901037d57Smrg
890f2e35a3aSmrg	    return 1;
891f2e35a3aSmrg	}
892f2e35a3aSmrg    }
89301037d57Smrg}
89401037d57Smrg
895f2e35a3aSmrg/* FIXME: with so many possible colors we need to determine
896f2e35a3aSmrg * when to free them to be nice to PseudoColor displays
897f2e35a3aSmrg */
89801037d57Smrgstatic Pixel
89901037d57Smrgcolor_register_to_xpixel(const ColorRegister *reg, XtermWidget xw)
90001037d57Smrg{
901f2e35a3aSmrg    Pixel result;
902f2e35a3aSmrg    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
903f2e35a3aSmrg    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
904f2e35a3aSmrg    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
905f2e35a3aSmrg    const AllocatedColorRegister *search;
90601037d57Smrg
907f2e35a3aSmrg    for (search = allocated_colors[rr][gg][bb]; search; search = search->next) {
908f2e35a3aSmrg	if (search->r == reg->r &&
909f2e35a3aSmrg	    search->g == reg->g &&
910f2e35a3aSmrg	    search->b == reg->b) {
911f2e35a3aSmrg	    return search->pix;
912f2e35a3aSmrg	}
913f2e35a3aSmrg    }
91401037d57Smrg
915f2e35a3aSmrg    save_allocated_color(reg, xw, &result);
916f2e35a3aSmrg    return result;
917e0a2b6dfSmrg}
918e0a2b6dfSmrg
919e0a2b6dfSmrgstatic void
920894e0ac8Smrgrefresh_graphic(TScreen const *screen,
921894e0ac8Smrg		Graphic const *graphic,
92201037d57Smrg		ColorRegister *buffer,
92301037d57Smrg		int refresh_x,
92401037d57Smrg		int refresh_y,
92501037d57Smrg		int refresh_w,
92601037d57Smrg		int refresh_h,
92701037d57Smrg		int draw_x,
92801037d57Smrg		int draw_y,
92901037d57Smrg		int draw_w,
93001037d57Smrg		int draw_h)
931e0a2b6dfSmrg{
93201037d57Smrg    int const pw = graphic->pixw;
93301037d57Smrg    int const ph = graphic->pixh;
93401037d57Smrg    int const graph_x = graphic->charcol * FontWidth(screen);
93501037d57Smrg    int const graph_y = graphic->charrow * FontHeight(screen);
93601037d57Smrg    int const graph_w = graphic->actual_width;
93701037d57Smrg    int const graph_h = graphic->actual_height;
93801037d57Smrg    int const mw = graphic->max_width;
939e0a2b6dfSmrg    int r, c;
94001037d57Smrg    int holes, total, out_of_range;
94101037d57Smrg    RegisterNum regnum;
942e0a2b6dfSmrg
94301037d57Smrg    TRACE(("refreshing graphic %u from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d)\n",
94401037d57Smrg	   graphic->id,
94501037d57Smrg	   graph_x, graph_y, draw_w, draw_h,
946e0a2b6dfSmrg	   graphic->valid,
947e0a2b6dfSmrg	   graphic->actual_width,
948e0a2b6dfSmrg	   graphic->actual_height,
94901037d57Smrg	   pw, ph,
950e0a2b6dfSmrg	   graphic->max_width,
95101037d57Smrg	   graphic->max_height));
952e0a2b6dfSmrg
95301037d57Smrg    TRACE(("refresh pixmap starts at %d,%d\n", refresh_x, refresh_y));
954e0a2b6dfSmrg
955e0a2b6dfSmrg    holes = total = 0;
95601037d57Smrg    out_of_range = 0;
95701037d57Smrg    for (r = 0; r < graph_h; r++) {
95801037d57Smrg	int pmy = graph_y + r * ph;
959894e0ac8Smrg
96001037d57Smrg	if (pmy + ph - 1 < draw_y)
961894e0ac8Smrg	    continue;
96201037d57Smrg	if (pmy > draw_y + draw_h - 1)
96301037d57Smrg	    break;
964894e0ac8Smrg
965f2e35a3aSmrg	if (pmy < draw_y || pmy > draw_y + draw_h - 1 ||
966f2e35a3aSmrg	    pmy < refresh_y || pmy > refresh_y + refresh_h - 1) {
967f2e35a3aSmrg	    out_of_range++;
968f2e35a3aSmrg	    continue;
969f2e35a3aSmrg	}
970f2e35a3aSmrg
97101037d57Smrg	for (c = 0; c < graph_w; c++) {
97201037d57Smrg	    int pmx = graph_x + c * pw;
973e0a2b6dfSmrg
97401037d57Smrg	    if (pmx + pw - 1 < draw_x)
975894e0ac8Smrg		continue;
97601037d57Smrg	    if (pmx > draw_x + draw_w - 1)
97701037d57Smrg		break;
978e0a2b6dfSmrg
979f2e35a3aSmrg	    if (pmx < draw_x || pmx > draw_x + draw_w - 1 ||
980f2e35a3aSmrg		pmx < refresh_x || pmx > refresh_x + refresh_w - 1) {
981f2e35a3aSmrg		out_of_range++;
982f2e35a3aSmrg		continue;
983f2e35a3aSmrg	    }
984f2e35a3aSmrg
985e0a2b6dfSmrg	    total++;
98601037d57Smrg	    regnum = graphic->pixels[r * mw + c];
98701037d57Smrg	    if (regnum == COLOR_HOLE) {
988e0a2b6dfSmrg		holes++;
989f2e35a3aSmrg	    } else {
990f2e35a3aSmrg		buffer[(pmy - refresh_y) * refresh_w +
991f2e35a3aSmrg		       (pmx - refresh_x)] =
992f2e35a3aSmrg		    graphic->color_registers[regnum];
993e0a2b6dfSmrg	    }
994e0a2b6dfSmrg	}
995894e0ac8Smrg    }
996e0a2b6dfSmrg
99701037d57Smrg    TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes; %d were out of pixmap range\n",
99801037d57Smrg	   holes, total, out_of_range));
99901037d57Smrg}
100001037d57Smrg
1001e0a2b6dfSmrg#ifdef DEBUG_REFRESH
100201037d57Smrg
100301037d57Smrg#define BASEX(X) ( (draw_x - base_x) + (X) )
100401037d57Smrg#define BASEY(Y) ( (draw_y - base_y) + (Y) )
100501037d57Smrg
100601037d57Smrgstatic void
100701037d57Smrgoutline_refresh(TScreen const *screen,
100801037d57Smrg		Graphic const *graphic,
100901037d57Smrg		Pixmap output_pm,
101001037d57Smrg		GC graphics_gc,
101101037d57Smrg		int base_x,
101201037d57Smrg		int base_y,
101301037d57Smrg		int draw_x,
101401037d57Smrg		int draw_y,
101501037d57Smrg		int draw_w,
101601037d57Smrg		int draw_h)
101701037d57Smrg{
101801037d57Smrg    Display *const display = screen->display;
101901037d57Smrg    int const pw = graphic->pixw;
102001037d57Smrg    int const ph = graphic->pixh;
102101037d57Smrg    XGCValues xgcv;
102201037d57Smrg    XColor def;
102301037d57Smrg
102401037d57Smrg    def.red = (unsigned short) ((1.0 - 0.1 * (rand() / (double)
102501037d57Smrg					      RAND_MAX) * 65535.0));
102601037d57Smrg    def.green = (unsigned short) ((0.7 + 0.2 * (rand() / (double)
102701037d57Smrg						RAND_MAX)) * 65535.0);
102801037d57Smrg    def.blue = (unsigned short) ((0.1 + 0.1 * (rand() / (double)
102901037d57Smrg					       RAND_MAX)) * 65535.0);
103001037d57Smrg    def.flags = DoRed | DoGreen | DoBlue;
103101037d57Smrg    if (allocateBestRGB(graphic->xw, &def)) {
103201037d57Smrg	xgcv.foreground = def.pixel;
103301037d57Smrg	XChangeGC(display, graphics_gc, GCForeground, &xgcv);
1034e0a2b6dfSmrg    }
1035e0a2b6dfSmrg
103601037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
103701037d57Smrg	      BASEX(0), BASEY(0),
103801037d57Smrg	      BASEX(draw_w - 1), BASEY(0));
103901037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
104001037d57Smrg	      BASEX(0), BASEY(draw_h - 1),
104101037d57Smrg	      BASEX(draw_w - 1), BASEY(draw_h - 1));
104201037d57Smrg
104301037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
104401037d57Smrg	      BASEX(0), BASEY(0),
104501037d57Smrg	      BASEX(0), BASEY(draw_h - 1));
104601037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
104701037d57Smrg	      BASEX(draw_w - 1), BASEY(0),
104801037d57Smrg	      BASEX(draw_w - 1), BASEY(draw_h - 1));
104901037d57Smrg
105001037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
105101037d57Smrg	      BASEX(draw_w - 1), BASEY(0),
105201037d57Smrg	      BASEX(0), BASEY(draw_h - 1));
105301037d57Smrg    XDrawLine(display, output_pm, graphics_gc,
105401037d57Smrg	      BASEX(draw_w - 1), BASEY(draw_h - 1),
105501037d57Smrg	      BASEX(0), BASEY(0));
105601037d57Smrg
1057ad37e533Smrg    def.red = (short) (0.7 * MAX_U_COLOR);
1058ad37e533Smrg    def.green = (short) (0.1 * MAX_U_COLOR);
1059ad37e533Smrg    def.blue = (short) (1.0 * MAX_U_COLOR);
106001037d57Smrg    def.flags = DoRed | DoGreen | DoBlue;
106101037d57Smrg    if (allocateBestRGB(graphic->xw, &def)) {
106201037d57Smrg	xgcv.foreground = def.pixel;
106301037d57Smrg	XChangeGC(display, graphics_gc, GCForeground, &xgcv);
106401037d57Smrg    }
106501037d57Smrg    XFillRectangle(display, output_pm, graphics_gc,
106601037d57Smrg		   BASEX(0),
106701037d57Smrg		   BASEY(0),
106801037d57Smrg		   (unsigned) pw, (unsigned) ph);
106901037d57Smrg    XFillRectangle(display, output_pm, graphics_gc,
107001037d57Smrg		   BASEX(draw_w - 1 - pw),
107101037d57Smrg		   BASEY(draw_h - 1 - ph),
107201037d57Smrg		   (unsigned) pw, (unsigned) ph);
1073e0a2b6dfSmrg}
107401037d57Smrg#endif
1075e0a2b6dfSmrg
1076e0a2b6dfSmrg/*
1077e0a2b6dfSmrg * Primary color hues:
1078e0a2b6dfSmrg *  blue:    0 degrees
1079e0a2b6dfSmrg *  red:   120 degrees
1080e0a2b6dfSmrg *  green: 240 degrees
1081e0a2b6dfSmrg */
1082894e0ac8Smrgvoid
1083e0a2b6dfSmrghls2rgb(int h, int l, int s, short *r, short *g, short *b)
1084e0a2b6dfSmrg{
108501037d57Smrg    const int hs = ((h + 240) / 60) % 6;
108601037d57Smrg    const double lv = l / 100.0;
108701037d57Smrg    const double sv = s / 100.0;
1088894e0ac8Smrg    double c, x, m, c2;
1089e0a2b6dfSmrg    double r1, g1, b1;
1090e0a2b6dfSmrg
1091e0a2b6dfSmrg    if (s == 0) {
1092e0a2b6dfSmrg	*r = *g = *b = (short) l;
1093e0a2b6dfSmrg	return;
1094e0a2b6dfSmrg    }
1095e0a2b6dfSmrg
109601037d57Smrg    c2 = (2.0 * lv) - 1.0;
109701037d57Smrg    if (c2 < 0.0)
1098894e0ac8Smrg	c2 = -c2;
1099894e0ac8Smrg    c = (1.0 - c2) * sv;
110001037d57Smrg    x = (hs & 1) ? c : 0.0;
1101e0a2b6dfSmrg    m = lv - 0.5 * c;
1102e0a2b6dfSmrg
110301037d57Smrg    switch (hs) {
1104e0a2b6dfSmrg    case 0:
1105e0a2b6dfSmrg	r1 = c;
1106e0a2b6dfSmrg	g1 = x;
1107e0a2b6dfSmrg	b1 = 0.0;
1108e0a2b6dfSmrg	break;
1109e0a2b6dfSmrg    case 1:
1110e0a2b6dfSmrg	r1 = x;
1111e0a2b6dfSmrg	g1 = c;
1112e0a2b6dfSmrg	b1 = 0.0;
1113e0a2b6dfSmrg	break;
1114e0a2b6dfSmrg    case 2:
1115e0a2b6dfSmrg	r1 = 0.0;
1116e0a2b6dfSmrg	g1 = c;
1117e0a2b6dfSmrg	b1 = x;
1118e0a2b6dfSmrg	break;
1119e0a2b6dfSmrg    case 3:
1120e0a2b6dfSmrg	r1 = 0.0;
1121e0a2b6dfSmrg	g1 = x;
1122e0a2b6dfSmrg	b1 = c;
1123e0a2b6dfSmrg	break;
1124e0a2b6dfSmrg    case 4:
1125e0a2b6dfSmrg	r1 = x;
1126e0a2b6dfSmrg	g1 = 0.0;
1127e0a2b6dfSmrg	b1 = c;
1128e0a2b6dfSmrg	break;
1129e0a2b6dfSmrg    case 5:
1130e0a2b6dfSmrg	r1 = c;
1131e0a2b6dfSmrg	g1 = 0.0;
1132e0a2b6dfSmrg	b1 = x;
1133e0a2b6dfSmrg	break;
1134e0a2b6dfSmrg    default:
1135894e0ac8Smrg	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
1136e0a2b6dfSmrg	*r = (short) 100;
1137e0a2b6dfSmrg	*g = (short) 100;
1138e0a2b6dfSmrg	*b = (short) 100;
1139e0a2b6dfSmrg	return;
1140e0a2b6dfSmrg    }
1141e0a2b6dfSmrg
1142e0a2b6dfSmrg    *r = (short) ((r1 + m) * 100.0 + 0.5);
1143e0a2b6dfSmrg    *g = (short) ((g1 + m) * 100.0 + 0.5);
1144e0a2b6dfSmrg    *b = (short) ((b1 + m) * 100.0 + 0.5);
1145e0a2b6dfSmrg
1146e0a2b6dfSmrg    if (*r < 0)
1147e0a2b6dfSmrg	*r = 0;
1148e0a2b6dfSmrg    else if (*r > 100)
1149e0a2b6dfSmrg	*r = 100;
1150e0a2b6dfSmrg    if (*g < 0)
1151e0a2b6dfSmrg	*g = 0;
1152e0a2b6dfSmrg    else if (*g > 100)
1153e0a2b6dfSmrg	*g = 100;
1154e0a2b6dfSmrg    if (*b < 0)
1155e0a2b6dfSmrg	*b = 0;
1156e0a2b6dfSmrg    else if (*b > 100)
1157e0a2b6dfSmrg	*b = 100;
1158e0a2b6dfSmrg}
1159e0a2b6dfSmrg
1160894e0ac8Smrgvoid
1161894e0ac8Smrgdump_graphic(Graphic const *graphic)
1162e0a2b6dfSmrg{
1163894e0ac8Smrg#if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
1164894e0ac8Smrg    RegisterNum color;
1165894e0ac8Smrg#endif
1166894e0ac8Smrg#ifdef DUMP_BITMAP
1167894e0ac8Smrg    int r, c;
1168894e0ac8Smrg    ColorRegister const *reg;
1169894e0ac8Smrg#endif
1170e0a2b6dfSmrg
1171894e0ac8Smrg    (void) graphic;
1172e0a2b6dfSmrg
1173894e0ac8Smrg    TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
1174894e0ac8Smrg	   graphic->id,
1175894e0ac8Smrg	   graphic->charrow,
1176894e0ac8Smrg	   graphic->charcol,
1177894e0ac8Smrg	   graphic->actual_width,
1178894e0ac8Smrg	   graphic->actual_height,
1179894e0ac8Smrg	   graphic->pixw,
1180894e0ac8Smrg	   graphic->pixh));
1181e0a2b6dfSmrg
1182894e0ac8Smrg#ifdef DUMP_COLORS
1183894e0ac8Smrg    TRACE(("graphic colors:\n"));
1184894e0ac8Smrg    for (color = 0; color < graphic->valid_registers; color++) {
1185894e0ac8Smrg	TRACE(("%03u: %d,%d,%d\n",
1186894e0ac8Smrg	       color,
1187894e0ac8Smrg	       graphic->color_registers[color].r,
1188894e0ac8Smrg	       graphic->color_registers[color].g,
1189894e0ac8Smrg	       graphic->color_registers[color].b));
1190e0a2b6dfSmrg    }
1191e0a2b6dfSmrg#endif
1192e0a2b6dfSmrg
1193894e0ac8Smrg#ifdef DUMP_BITMAP
1194894e0ac8Smrg    TRACE(("graphic pixels:\n"));
1195894e0ac8Smrg    for (r = 0; r < graphic->actual_height; r++) {
1196894e0ac8Smrg	for (c = 0; c < graphic->actual_width; c++) {
1197894e0ac8Smrg	    color = graphic->pixels[r * graphic->max_width + c];
1198894e0ac8Smrg	    if (color == COLOR_HOLE) {
1199894e0ac8Smrg		TRACE(("?"));
1200e0a2b6dfSmrg	    } else {
1201894e0ac8Smrg		reg = &graphic->color_registers[color];
1202894e0ac8Smrg		if (reg->r + reg->g + reg->b > 200) {
1203894e0ac8Smrg		    TRACE(("#"));
1204894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 150) {
1205894e0ac8Smrg		    TRACE(("%%"));
1206894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 100) {
1207894e0ac8Smrg		    TRACE((":"));
1208894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 80) {
1209894e0ac8Smrg		    TRACE(("."));
1210894e0ac8Smrg		} else {
1211894e0ac8Smrg		    TRACE((" "));
1212e0a2b6dfSmrg		}
1213e0a2b6dfSmrg	    }
1214e0a2b6dfSmrg	}
1215894e0ac8Smrg	TRACE(("\n"));
1216e0a2b6dfSmrg    }
1217e0a2b6dfSmrg
1218894e0ac8Smrg    TRACE(("\n"));
1219894e0ac8Smrg#endif
1220e0a2b6dfSmrg}
1221e0a2b6dfSmrg
1222e0a2b6dfSmrg/* Erase the portion of any displayed graphic overlapping with a rectangle
122301037d57Smrg * of the given size and location in pixels relative to the start of the
122401037d57Smrg * graphic.  This is used to allow text to "erase" graphics underneath it.
1225e0a2b6dfSmrg */
1226e0a2b6dfSmrgstatic void
1227894e0ac8Smrgerase_graphic(Graphic *graphic, int x, int y, int w, int h)
1228e0a2b6dfSmrg{
1229e0a2b6dfSmrg    RegisterNum hole = COLOR_HOLE;
1230e0a2b6dfSmrg    int pw, ph;
1231e0a2b6dfSmrg    int r, c;
1232894e0ac8Smrg    int rbase, cbase;
1233e0a2b6dfSmrg
1234e0a2b6dfSmrg    pw = graphic->pixw;
1235e0a2b6dfSmrg    ph = graphic->pixh;
1236e0a2b6dfSmrg
1237894e0ac8Smrg    TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
1238e0a2b6dfSmrg
1239894e0ac8Smrg    rbase = 0;
1240e0a2b6dfSmrg    for (r = 0; r < graphic->actual_height; r++) {
1241894e0ac8Smrg	if (rbase + ph - 1 >= y
1242894e0ac8Smrg	    && rbase <= y + h - 1) {
1243894e0ac8Smrg	    cbase = 0;
1244894e0ac8Smrg	    for (c = 0; c < graphic->actual_width; c++) {
1245894e0ac8Smrg		if (cbase + pw - 1 >= x
1246894e0ac8Smrg		    && cbase <= x + w - 1) {
1247894e0ac8Smrg		    graphic->pixels[r * graphic->max_width + c] = hole;
1248894e0ac8Smrg		}
1249894e0ac8Smrg		cbase += pw;
1250894e0ac8Smrg	    }
1251e0a2b6dfSmrg	}
1252894e0ac8Smrg	rbase += ph;
1253e0a2b6dfSmrg    }
1254e0a2b6dfSmrg}
1255e0a2b6dfSmrg
1256e0a2b6dfSmrgstatic int
1257894e0ac8Smrgcompare_graphic_ids(const void *left, const void *right)
1258e0a2b6dfSmrg{
1259894e0ac8Smrg    const Graphic *l = *(const Graphic *const *) left;
1260894e0ac8Smrg    const Graphic *r = *(const Graphic *const *) right;
1261e0a2b6dfSmrg
1262e0a2b6dfSmrg    if (!l->valid || !r->valid)
1263e0a2b6dfSmrg	return 0;
126401037d57Smrg
126501037d57Smrg    if (l->bufferid < r->bufferid)
126601037d57Smrg	return -1;
126701037d57Smrg    else if (l->bufferid > r->bufferid)
126801037d57Smrg	return 1;
126901037d57Smrg
1270e0a2b6dfSmrg    if (l->id < r->id)
1271e0a2b6dfSmrg	return -1;
1272e0a2b6dfSmrg    else
1273e0a2b6dfSmrg	return 1;
1274e0a2b6dfSmrg}
1275e0a2b6dfSmrg
127601037d57Smrgstatic void
127701037d57Smrgclip_area(int *orig_x, int *orig_y, int *orig_w, int *orig_h,
127801037d57Smrg	  int clip_x, int clip_y, int clip_w, int clip_h)
1279e0a2b6dfSmrg{
128001037d57Smrg    if (*orig_x < clip_x) {
128101037d57Smrg	const int diff = clip_x - *orig_x;
128201037d57Smrg	*orig_x += diff;
128301037d57Smrg	*orig_w -= diff;
128401037d57Smrg    }
128501037d57Smrg    if (*orig_w > 0 && *orig_x + *orig_w > clip_x + clip_w) {
128601037d57Smrg	*orig_w -= (*orig_x + *orig_w) - (clip_x + clip_w);
128701037d57Smrg    }
128801037d57Smrg
128901037d57Smrg    if (*orig_y < clip_y) {
129001037d57Smrg	const int diff = clip_y - *orig_y;
129101037d57Smrg	*orig_y += diff;
129201037d57Smrg	*orig_h -= diff;
129301037d57Smrg    }
129401037d57Smrg    if (*orig_h > 0 && *orig_y + *orig_h > clip_y + clip_h) {
129501037d57Smrg	*orig_h -= (*orig_y + *orig_h) - (clip_y + clip_h);
129601037d57Smrg    }
129701037d57Smrg}
129801037d57Smrg
129901037d57Smrg/* the coordinates are relative to the screen */
130001037d57Smrgstatic void
130101037d57Smrgrefresh_graphics(XtermWidget xw,
130201037d57Smrg		 int leftcol,
130301037d57Smrg		 int toprow,
130401037d57Smrg		 int ncols,
130501037d57Smrg		 int nrows,
130601037d57Smrg		 int skip_clean)
130701037d57Smrg{
130801037d57Smrg    TScreen *const screen = TScreenOf(xw);
130901037d57Smrg    Display *const display = screen->display;
131001037d57Smrg    Window const drawable = VDrawable(screen);
131101037d57Smrg    int const scroll_y = screen->topline * FontHeight(screen);
131201037d57Smrg    int const refresh_x = leftcol * FontWidth(screen);
131301037d57Smrg    int const refresh_y = toprow * FontHeight(screen) + scroll_y;
131401037d57Smrg    int const refresh_w = ncols * FontWidth(screen);
131501037d57Smrg    int const refresh_h = nrows * FontHeight(screen);
131601037d57Smrg    int draw_x_min, draw_x_max;
131701037d57Smrg    int draw_y_min, draw_y_max;
1318894e0ac8Smrg    Graphic *ordered_graphics[MAX_GRAPHICS];
131901037d57Smrg    unsigned ii, jj;
132001037d57Smrg    unsigned active_count;
132101037d57Smrg    unsigned holes, non_holes;
132201037d57Smrg    int xx, yy;
132301037d57Smrg    ColorRegister *buffer;
1324e0a2b6dfSmrg
132501037d57Smrg    active_count = 0;
1326894e0ac8Smrg    FOR_EACH_SLOT(ii) {
132701037d57Smrg	Graphic *graphic;
132801037d57Smrg	if (!(graphic = getActiveSlot(ii)))
132901037d57Smrg	    continue;
133001037d57Smrg	TRACE(("refreshing graphic %d on buffer %d, current buffer %d\n",
133101037d57Smrg	       graphic->id, graphic->bufferid, screen->whichBuf));
133201037d57Smrg	if (screen->whichBuf == 0) {
133301037d57Smrg	    if (graphic->bufferid != 0) {
133401037d57Smrg		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d\n",
133501037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
133601037d57Smrg		continue;
133701037d57Smrg	    }
133801037d57Smrg	} else {
133901037d57Smrg	    if (graphic->bufferid == 0 && graphic->charrow >= 0) {
134001037d57Smrg		TRACE(("skipping graphic %d from normal buffer (%d) when drawing screen=%d because it is not in scrollback area\n",
134101037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
134201037d57Smrg		continue;
134301037d57Smrg	    }
134401037d57Smrg	    if (graphic->bufferid == 1 &&
134501037d57Smrg		graphic->charrow + (graphic->actual_height +
134601037d57Smrg				    FontHeight(screen) - 1) /
134701037d57Smrg		FontHeight(screen) < 0) {
134801037d57Smrg		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d because it is completely in scrollback area\n",
134901037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
135001037d57Smrg		continue;
135101037d57Smrg	    }
1352894e0ac8Smrg	}
13532e4f8982Smrg	if (graphic->hidden)
13542e4f8982Smrg	    continue;
135501037d57Smrg	ordered_graphics[active_count++] = graphic;
1356894e0ac8Smrg    }
135701037d57Smrg
135801037d57Smrg    if (active_count == 0)
135901037d57Smrg	return;
136001037d57Smrg    if (active_count > 1) {
1361894e0ac8Smrg	qsort(ordered_graphics,
136201037d57Smrg	      (size_t) active_count,
1363894e0ac8Smrg	      sizeof(ordered_graphics[0]),
1364894e0ac8Smrg	      compare_graphic_ids);
1365e0a2b6dfSmrg    }
1366e0a2b6dfSmrg
136701037d57Smrg    if (skip_clean) {
136801037d57Smrg	unsigned skip_count;
1369e0a2b6dfSmrg
137001037d57Smrg	for (jj = 0; jj < active_count; ++jj) {
137101037d57Smrg	    if (ordered_graphics[jj]->dirty)
137201037d57Smrg		break;
1373e0a2b6dfSmrg	}
137401037d57Smrg	skip_count = jj;
137501037d57Smrg	if (skip_count == active_count)
137601037d57Smrg	    return;
1377e0a2b6dfSmrg
137801037d57Smrg	active_count -= skip_count;
137901037d57Smrg	for (jj = 0; jj < active_count; ++jj) {
138001037d57Smrg	    ordered_graphics[jj] = ordered_graphics[jj + skip_count];
138101037d57Smrg	}
1382e0a2b6dfSmrg    }
1383e0a2b6dfSmrg
138401037d57Smrg    if (!(buffer = malloc(sizeof(*buffer) *
138501037d57Smrg			  (unsigned) refresh_w * (unsigned) refresh_h))) {
138601037d57Smrg	TRACE(("unable to allocate %dx%d buffer for graphics refresh\n",
138701037d57Smrg	       refresh_w, refresh_h));
138801037d57Smrg	return;
138901037d57Smrg    }
139001037d57Smrg    for (yy = 0; yy < refresh_h; yy++) {
139101037d57Smrg	for (xx = 0; xx < refresh_w; xx++) {
1392f2e35a3aSmrg	    buffer[yy * refresh_w + xx] = null_color;
139301037d57Smrg	}
139401037d57Smrg    }
1395e0a2b6dfSmrg
139601037d57Smrg    TRACE(("refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d (%d,%d %dx%d)\n",
139701037d57Smrg	   screen->topline,
139801037d57Smrg	   leftcol, toprow,
139901037d57Smrg	   nrows, ncols,
140001037d57Smrg	   refresh_x, refresh_y,
140101037d57Smrg	   refresh_w, refresh_h));
1402e0a2b6dfSmrg
140301037d57Smrg    {
140401037d57Smrg	int const altarea_x = 0;
140501037d57Smrg	int const altarea_y = 0;
140601037d57Smrg	int const altarea_w = Width(screen) * FontWidth(screen);
140701037d57Smrg	int const altarea_h = Height(screen) * FontHeight(screen);
140801037d57Smrg
140901037d57Smrg	int const scrollarea_x = 0;
141001037d57Smrg	int const scrollarea_y = scroll_y;
141101037d57Smrg	int const scrollarea_w = Width(screen) * FontWidth(screen);
141201037d57Smrg	int const scrollarea_h = -scroll_y;
141301037d57Smrg
141401037d57Smrg	int const mainarea_x = 0;
141501037d57Smrg	int const mainarea_y = scroll_y;
141601037d57Smrg	int const mainarea_w = Width(screen) * FontWidth(screen);
141701037d57Smrg	int const mainarea_h = -scroll_y + Height(screen) * FontHeight(screen);
141801037d57Smrg
141901037d57Smrg	draw_x_min = refresh_x + refresh_w;
142001037d57Smrg	draw_x_max = refresh_x - 1;
142101037d57Smrg	draw_y_min = refresh_y + refresh_h;
142201037d57Smrg	draw_y_max = refresh_y - 1;
142301037d57Smrg	for (jj = 0; jj < active_count; ++jj) {
142401037d57Smrg	    Graphic *graphic = ordered_graphics[jj];
142501037d57Smrg	    int draw_x = graphic->charcol * FontWidth(screen);
142601037d57Smrg	    int draw_y = graphic->charrow * FontHeight(screen);
142701037d57Smrg	    int draw_w = graphic->actual_width;
142801037d57Smrg	    int draw_h = graphic->actual_height;
142901037d57Smrg
143001037d57Smrg	    if (screen->whichBuf != 0) {
143101037d57Smrg		if (graphic->bufferid != 0) {
143201037d57Smrg		    /* clip to alt buffer */
143301037d57Smrg		    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
143401037d57Smrg			      altarea_x, altarea_y, altarea_w, altarea_h);
14352e4f8982Smrg		} else {
143601037d57Smrg		    /* clip to scrollback area */
143701037d57Smrg		    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
143801037d57Smrg			      scrollarea_x, scrollarea_y,
143901037d57Smrg			      scrollarea_w, scrollarea_h);
144001037d57Smrg		}
144101037d57Smrg	    } else {
144201037d57Smrg		/* clip to scrollback + normal area */
144301037d57Smrg		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
144401037d57Smrg			  mainarea_x, mainarea_y,
144501037d57Smrg			  mainarea_w, mainarea_h);
144601037d57Smrg	    }
144701037d57Smrg
144801037d57Smrg	    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
144901037d57Smrg		      refresh_x, refresh_y, refresh_w, refresh_h);
145001037d57Smrg
145101037d57Smrg	    TRACE(("refresh: graph=%u\n", jj));
145201037d57Smrg	    TRACE(("         refresh_x=%d refresh_y=%d refresh_w=%d refresh_h=%d\n",
145301037d57Smrg		   refresh_x, refresh_y, refresh_w, refresh_h));
145401037d57Smrg	    TRACE(("         draw_x=%d draw_y=%d draw_w=%d draw_h=%d\n",
145501037d57Smrg		   draw_x, draw_y, draw_w, draw_h));
145601037d57Smrg
145701037d57Smrg	    if (draw_w > 0 && draw_h > 0) {
145801037d57Smrg		refresh_graphic(screen, graphic, buffer,
145901037d57Smrg				refresh_x, refresh_y,
146001037d57Smrg				refresh_w, refresh_h,
146101037d57Smrg				draw_x, draw_y,
146201037d57Smrg				draw_w, draw_h);
146301037d57Smrg		if (draw_x < draw_x_min)
146401037d57Smrg		    draw_x_min = draw_x;
146501037d57Smrg		if (draw_x + draw_w - 1 > draw_x_max)
146601037d57Smrg		    draw_x_max = draw_x + draw_w - 1;
146701037d57Smrg		if (draw_y < draw_y_min)
146801037d57Smrg		    draw_y_min = draw_y;
146901037d57Smrg		if (draw_y + draw_h - 1 > draw_y_max)
147001037d57Smrg		    draw_y_max = draw_y + draw_h - 1;
147101037d57Smrg	    }
147201037d57Smrg	    graphic->dirty = 0;
1473e0a2b6dfSmrg	}
147401037d57Smrg    }
1475e0a2b6dfSmrg
147601037d57Smrg    if (draw_x_max < refresh_x ||
147701037d57Smrg	draw_x_min > refresh_x + refresh_w - 1 ||
147801037d57Smrg	draw_y_max < refresh_y ||
147901037d57Smrg	draw_y_min > refresh_y + refresh_h - 1) {
148001037d57Smrg	free(buffer);
148101037d57Smrg	return;
1482e0a2b6dfSmrg    }
148301037d57Smrg
148401037d57Smrg    holes = 0U;
148501037d57Smrg    non_holes = 0U;
1486f2e35a3aSmrg    {
1487f2e35a3aSmrg	int y_min = draw_y_min - refresh_y;
1488f2e35a3aSmrg	int y_max = draw_y_max - refresh_y;
1489f2e35a3aSmrg	int x_min = draw_x_min - refresh_x;
1490f2e35a3aSmrg	int x_max = draw_x_max - refresh_x;
1491f2e35a3aSmrg	const ColorRegister *base = buffer + (y_min * refresh_w);
1492f2e35a3aSmrg
1493f2e35a3aSmrg	for (yy = y_min; yy <= y_max; yy++) {
1494f2e35a3aSmrg	    const ColorRegister *scan = base + x_min;
1495f2e35a3aSmrg	    for (xx = x_min; xx <= x_max; xx++) {
1496f2e35a3aSmrg		if (scan->r < 0 || scan->g < 0 || scan->b < 0) {
1497f2e35a3aSmrg		    holes++;
1498f2e35a3aSmrg		} else {
1499f2e35a3aSmrg		    non_holes++;
1500f2e35a3aSmrg		}
1501f2e35a3aSmrg		++scan;
150201037d57Smrg	    }
1503f2e35a3aSmrg	    base += refresh_w;
150401037d57Smrg	}
150501037d57Smrg    }
150601037d57Smrg
150701037d57Smrg    if (non_holes < 1U) {
150801037d57Smrg	TRACE(("refresh: visible graphics areas are erased; nothing to do\n"));
150901037d57Smrg	free(buffer);
151001037d57Smrg	return;
151101037d57Smrg    }
151201037d57Smrg
151301037d57Smrg    /*
151401037d57Smrg     * If we have any holes we can't just copy an image rectangle, and masking
151501037d57Smrg     * with bitmaps is very expensive.  This fallback is surprisingly faster
151601037d57Smrg     * than the XPutImage version in some cases, but I don't know why.
151701037d57Smrg     * (This is even though there's no X11 primitive for drawing a horizontal
151801037d57Smrg     * line of height one and no attempt is made to handle multiple lines at
151901037d57Smrg     * once.)
152001037d57Smrg     */
152101037d57Smrg    if (holes > 0U) {
152201037d57Smrg	GC graphics_gc;
152301037d57Smrg	XGCValues xgcv;
152401037d57Smrg	ColorRegister last_color;
152501037d57Smrg	ColorRegister gc_color;
152601037d57Smrg	int run;
152701037d57Smrg
152801037d57Smrg	memset(&xgcv, 0, sizeof(xgcv));
152901037d57Smrg	xgcv.graphics_exposures = False;
153001037d57Smrg	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
153101037d57Smrg	if (graphics_gc == None) {
153201037d57Smrg	    TRACE(("unable to allocate GC for graphics refresh\n"));
153301037d57Smrg	    free(buffer);
153401037d57Smrg	    return;
153501037d57Smrg	}
153601037d57Smrg
1537f2e35a3aSmrg	last_color = null_color;
1538f2e35a3aSmrg	gc_color = null_color;
153901037d57Smrg	run = 0;
154001037d57Smrg	for (yy = draw_y_min - refresh_y; yy <= draw_y_max - refresh_y; yy++) {
154101037d57Smrg	    for (xx = draw_x_min - refresh_x; xx <= draw_x_max - refresh_x;
154201037d57Smrg		 xx++) {
154301037d57Smrg		const ColorRegister color = buffer[yy * refresh_w + xx];
154401037d57Smrg
154501037d57Smrg		if (color.r < 0 || color.g < 0 || color.b < 0) {
154601037d57Smrg		    last_color = color;
154701037d57Smrg		    if (run > 0) {
154801037d57Smrg			XDrawLine(display, drawable, graphics_gc,
154901037d57Smrg				  OriginX(screen) + refresh_x + xx - run,
155001037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy,
155101037d57Smrg				  OriginX(screen) + refresh_x + xx - 1,
155201037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy);
155301037d57Smrg			run = 0;
155401037d57Smrg		    }
155501037d57Smrg		    continue;
155601037d57Smrg		}
155701037d57Smrg
1558f2e35a3aSmrg		if (DiffColor(color, last_color)) {
155901037d57Smrg		    last_color = color;
156001037d57Smrg		    if (run > 0) {
156101037d57Smrg			XDrawLine(display, drawable, graphics_gc,
156201037d57Smrg				  OriginX(screen) + refresh_x + xx - run,
156301037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy,
156401037d57Smrg				  OriginX(screen) + refresh_x + xx - 1,
156501037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy);
156601037d57Smrg			run = 0;
156701037d57Smrg		    }
156801037d57Smrg
1569f2e35a3aSmrg		    if (DiffColor(color, gc_color)) {
157001037d57Smrg			xgcv.foreground =
157101037d57Smrg			    color_register_to_xpixel(&color, xw);
157201037d57Smrg			XChangeGC(display, graphics_gc, GCForeground, &xgcv);
157301037d57Smrg			gc_color = color;
157401037d57Smrg		    }
157501037d57Smrg		}
157601037d57Smrg		run++;
157701037d57Smrg	    }
157801037d57Smrg	    if (run > 0) {
1579f2e35a3aSmrg		last_color = null_color;
158001037d57Smrg		XDrawLine(display, drawable, graphics_gc,
158101037d57Smrg			  OriginX(screen) + refresh_x + xx - run,
158201037d57Smrg			  (OriginY(screen) - scroll_y) + refresh_y + yy,
158301037d57Smrg			  OriginX(screen) + refresh_x + xx - 1,
158401037d57Smrg			  (OriginY(screen) - scroll_y) + refresh_y + yy);
158501037d57Smrg		run = 0;
158601037d57Smrg	    }
158701037d57Smrg	}
158801037d57Smrg
158901037d57Smrg	XFreeGC(display, graphics_gc);
159001037d57Smrg    } else {
159101037d57Smrg	XGCValues xgcv;
159201037d57Smrg	GC graphics_gc;
1593f2e35a3aSmrg	ColorRegister old_colors[2];
1594f2e35a3aSmrg	Pixel fg, old_result[2];
159501037d57Smrg	XImage *image;
159601037d57Smrg	char *imgdata;
159701037d57Smrg	unsigned image_w, image_h;
1598f2e35a3aSmrg	int nn;
159901037d57Smrg
160001037d57Smrg	memset(&xgcv, 0, sizeof(xgcv));
160101037d57Smrg	xgcv.graphics_exposures = False;
160201037d57Smrg	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
160301037d57Smrg	if (graphics_gc == None) {
160401037d57Smrg	    TRACE(("unable to allocate GC for graphics refresh\n"));
160501037d57Smrg	    free(buffer);
160601037d57Smrg	    return;
160701037d57Smrg	}
160801037d57Smrg
160901037d57Smrg	/* FIXME: is it worth reusing the GC/Image/imagedata across calls? */
161001037d57Smrg	/* FIXME: is it worth using shared memory when available? */
161101037d57Smrg	image_w = (unsigned) draw_x_max + 1U - (unsigned) draw_x_min;
161201037d57Smrg	image_h = (unsigned) draw_y_max + 1U - (unsigned) draw_y_min;
161301037d57Smrg	image = XCreateImage(display, xw->visInfo->visual,
161401037d57Smrg			     (unsigned) xw->visInfo->depth,
161501037d57Smrg			     ZPixmap, 0, NULL,
161601037d57Smrg			     image_w, image_h,
1617f2e35a3aSmrg			     (int) (sizeof(int) * 8U), 0);
161801037d57Smrg	if (!image) {
161901037d57Smrg	    TRACE(("unable to allocate XImage for graphics refresh\n"));
162001037d57Smrg	    XFreeGC(display, graphics_gc);
162101037d57Smrg	    free(buffer);
162201037d57Smrg	    return;
162301037d57Smrg	}
1624f2e35a3aSmrg	imgdata = malloc((size_t) (image_h * (unsigned) image->bytes_per_line));
162501037d57Smrg	if (!imgdata) {
162601037d57Smrg	    TRACE(("unable to allocate XImage for graphics refresh\n"));
162701037d57Smrg	    XDestroyImage(image);
162801037d57Smrg	    XFreeGC(display, graphics_gc);
162901037d57Smrg	    free(buffer);
163001037d57Smrg	    return;
163101037d57Smrg	}
163201037d57Smrg	image->data = imgdata;
163301037d57Smrg
163401037d57Smrg	fg = 0U;
1635f2e35a3aSmrg	nn = 0;
1636f2e35a3aSmrg
1637f2e35a3aSmrg	/* two-level cache cuts down on lookup-calls */
1638f2e35a3aSmrg	old_result[0] = 0U;
1639f2e35a3aSmrg	old_result[1] = 0U;
1640f2e35a3aSmrg	old_colors[0] = null_color;
1641f2e35a3aSmrg	old_colors[1] = null_color;
1642f2e35a3aSmrg
164301037d57Smrg	for (yy = draw_y_min - refresh_y; yy <= draw_y_max - refresh_y; yy++) {
164401037d57Smrg	    for (xx = draw_x_min - refresh_x; xx <= draw_x_max - refresh_x;
164501037d57Smrg		 xx++) {
164601037d57Smrg		const ColorRegister color = buffer[yy * refresh_w + xx];
164701037d57Smrg
1648f2e35a3aSmrg		if (DiffColor(color, old_colors[nn])) {
1649f2e35a3aSmrg		    if (DiffColor(color, old_colors[!nn])) {
1650f2e35a3aSmrg			nn = !nn;
1651f2e35a3aSmrg			fg = color_register_to_xpixel(&color, xw);
1652f2e35a3aSmrg			old_result[nn] = fg;
1653f2e35a3aSmrg			old_colors[nn] = color;
1654f2e35a3aSmrg		    } else {
1655f2e35a3aSmrg			nn = !nn;
1656f2e35a3aSmrg			fg = old_result[nn];
1657f2e35a3aSmrg		    }
165801037d57Smrg		}
165901037d57Smrg
1660f2e35a3aSmrg		XPutPixel(image,
1661f2e35a3aSmrg			  xx + refresh_x - draw_x_min,
166201037d57Smrg			  yy + refresh_y - draw_y_min, fg);
166301037d57Smrg	    }
166401037d57Smrg	}
166501037d57Smrg
166601037d57Smrg	XPutImage(display, drawable, graphics_gc, image,
166701037d57Smrg		  0, 0,
166801037d57Smrg		  OriginX(screen) + draw_x_min,
166901037d57Smrg		  (OriginY(screen) - scroll_y) + draw_y_min,
167001037d57Smrg		  image_w, image_h);
167101037d57Smrg	free(imgdata);
167201037d57Smrg	image->data = NULL;
167301037d57Smrg	XDestroyImage(image);
167401037d57Smrg	XFreeGC(display, graphics_gc);
167501037d57Smrg    }
167601037d57Smrg
167701037d57Smrg    free(buffer);
167801037d57Smrg    XFlush(display);
167901037d57Smrg}
168001037d57Smrg
168101037d57Smrgvoid
168201037d57Smrgrefresh_displayed_graphics(XtermWidget xw,
168301037d57Smrg			   int leftcol,
168401037d57Smrg			   int toprow,
168501037d57Smrg			   int ncols,
168601037d57Smrg			   int nrows)
168701037d57Smrg{
168801037d57Smrg    refresh_graphics(xw, leftcol, toprow, ncols, nrows, 0);
168901037d57Smrg}
169001037d57Smrg
169101037d57Smrgvoid
169201037d57Smrgrefresh_modified_displayed_graphics(XtermWidget xw)
169301037d57Smrg{
169401037d57Smrg    TScreen const *screen = TScreenOf(xw);
169501037d57Smrg    refresh_graphics(xw, 0, 0, MaxCols(screen), MaxRows(screen), 1);
1696e0a2b6dfSmrg}
1697e0a2b6dfSmrg
1698894e0ac8Smrgvoid
169901037d57Smrgscroll_displayed_graphics(XtermWidget xw, int rows)
1700e0a2b6dfSmrg{
1701f2e35a3aSmrg    if (used_graphics) {
1702f2e35a3aSmrg	TScreen const *screen = TScreenOf(xw);
1703f2e35a3aSmrg	unsigned ii;
1704e0a2b6dfSmrg
1705f2e35a3aSmrg	TRACE(("graphics scroll: moving all up %d rows\n", rows));
1706f2e35a3aSmrg	/* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
1707894e0ac8Smrg
1708f2e35a3aSmrg	FOR_EACH_SLOT(ii) {
1709f2e35a3aSmrg	    Graphic *graphic;
17102e4f8982Smrg
1711f2e35a3aSmrg	    if (!(graphic = getActiveSlot(ii)))
1712f2e35a3aSmrg		continue;
1713f2e35a3aSmrg	    if (graphic->bufferid != screen->whichBuf)
1714f2e35a3aSmrg		continue;
1715f2e35a3aSmrg	    if (graphic->hidden)
1716f2e35a3aSmrg		continue;
1717e0a2b6dfSmrg
1718f2e35a3aSmrg	    graphic->charrow -= rows;
1719f2e35a3aSmrg	}
1720e0a2b6dfSmrg    }
1721e0a2b6dfSmrg}
1722e0a2b6dfSmrg
1723894e0ac8Smrgvoid
1724e0a2b6dfSmrgpixelarea_clear_displayed_graphics(TScreen const *screen,
1725e0a2b6dfSmrg				   int winx,
1726e0a2b6dfSmrg				   int winy,
1727e0a2b6dfSmrg				   int w,
1728e0a2b6dfSmrg				   int h)
1729e0a2b6dfSmrg{
1730894e0ac8Smrg    unsigned ii;
1731e0a2b6dfSmrg
1732f2e35a3aSmrg    if (!used_graphics)
1733f2e35a3aSmrg	return;
1734f2e35a3aSmrg
1735894e0ac8Smrg    FOR_EACH_SLOT(ii) {
173601037d57Smrg	Graphic *graphic;
173701037d57Smrg	/* FIXME: are these coordinates (scrolled) screen-relative? */
173801037d57Smrg	int const scroll_y = (screen->whichBuf == 0
173901037d57Smrg			      ? screen->topline * FontHeight(screen)
174001037d57Smrg			      : 0);
174101037d57Smrg	int graph_x;
174201037d57Smrg	int graph_y;
174301037d57Smrg	int x, y;
174401037d57Smrg
1745894e0ac8Smrg	if (!(graphic = getActiveSlot(ii)))
1746e0a2b6dfSmrg	    continue;
174701037d57Smrg	if (graphic->bufferid != screen->whichBuf)
174801037d57Smrg	    continue;
17492e4f8982Smrg	if (graphic->hidden)
17502e4f8982Smrg	    continue;
1751e0a2b6dfSmrg
175201037d57Smrg	graph_x = graphic->charcol * FontWidth(screen);
175301037d57Smrg	graph_y = graphic->charrow * FontHeight(screen);
175401037d57Smrg	x = winx - graph_x;
175501037d57Smrg	y = (winy - scroll_y) - graph_y;
1756e0a2b6dfSmrg
175701037d57Smrg	TRACE(("pixelarea clear graphics: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
1758e0a2b6dfSmrg	       screen->topline,
1759e0a2b6dfSmrg	       winx, winy,
1760e0a2b6dfSmrg	       w, h,
1761e0a2b6dfSmrg	       x, y));
1762894e0ac8Smrg	erase_graphic(graphic, x, y, w, h);
1763e0a2b6dfSmrg    }
1764e0a2b6dfSmrg}
1765e0a2b6dfSmrg
1766894e0ac8Smrgvoid
1767e0a2b6dfSmrgchararea_clear_displayed_graphics(TScreen const *screen,
1768e0a2b6dfSmrg				  int leftcol,
1769e0a2b6dfSmrg				  int toprow,
1770e0a2b6dfSmrg				  int ncols,
1771e0a2b6dfSmrg				  int nrows)
1772e0a2b6dfSmrg{
1773f2e35a3aSmrg    if (used_graphics) {
1774f2e35a3aSmrg	int const x = leftcol * FontWidth(screen);
1775f2e35a3aSmrg	int const y = toprow * FontHeight(screen);
1776f2e35a3aSmrg	int const w = ncols * FontWidth(screen);
1777f2e35a3aSmrg	int const h = nrows * FontHeight(screen);
1778e0a2b6dfSmrg
1779f2e35a3aSmrg	TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
1780f2e35a3aSmrg	       screen->topline,
1781f2e35a3aSmrg	       leftcol, toprow,
1782f2e35a3aSmrg	       nrows, ncols,
1783f2e35a3aSmrg	       x, y, w, h));
1784f2e35a3aSmrg	pixelarea_clear_displayed_graphics(screen, x, y, w, h);
1785f2e35a3aSmrg    }
1786e0a2b6dfSmrg}
1787e0a2b6dfSmrg
1788894e0ac8Smrgvoid
1789e0a2b6dfSmrgreset_displayed_graphics(TScreen const *screen)
1790e0a2b6dfSmrg{
1791f2e35a3aSmrg    init_color_registers(getSharedRegisters(), GraphicsTermId(screen));
1792894e0ac8Smrg
1793f2e35a3aSmrg    if (used_graphics) {
1794f2e35a3aSmrg	unsigned ii;
1795894e0ac8Smrg
1796f2e35a3aSmrg	TRACE(("resetting all graphics\n"));
1797f2e35a3aSmrg	FOR_EACH_SLOT(ii) {
1798f2e35a3aSmrg	    deactivateSlot(ii);
1799f2e35a3aSmrg	}
1800894e0ac8Smrg    }
1801894e0ac8Smrg}
1802894e0ac8Smrg
1803894e0ac8Smrg#ifdef NO_LEAKS
1804894e0ac8Smrgvoid
1805894e0ac8Smrgnoleaks_graphics(void)
1806894e0ac8Smrg{
1807894e0ac8Smrg    unsigned ii;
1808894e0ac8Smrg
1809894e0ac8Smrg    FOR_EACH_SLOT(ii) {
1810894e0ac8Smrg	deactivateSlot(ii);
1811e0a2b6dfSmrg    }
1812e0a2b6dfSmrg}
1813894e0ac8Smrg#endif
1814