15104ee6eSmrg/* $XTermId: graphics.c,v 1.137 2024/12/01 20:21:19 tom Exp $ */
2e0a2b6dfSmrg
3e0a2b6dfSmrg/*
404b94745Smrg * Copyright 2013-2023,2024 by Thomas E. Dickey
504b94745Smrg * Copyright 2013-2022,2023 by Ross Combs
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
4604b94745Smrg#define OPT_INHERIT_COLORS 0
4704b94745Smrg
4804b94745Smrg#if OPT_REGIS_GRAPHICS
4904b94745Smrg#include <graphics_regis.h>
5004b94745Smrg#endif
5104b94745Smrg
52894e0ac8Smrg#undef DUMP_BITMAP
53894e0ac8Smrg#undef DUMP_COLORS
54894e0ac8Smrg#undef DEBUG_PALETTE
55894e0ac8Smrg#undef DEBUG_PIXEL
56e0a2b6dfSmrg#undef DEBUG_REFRESH
57e0a2b6dfSmrg
5801037d57Smrg/*
5901037d57Smrg * graphics TODO list
6001037d57Smrg *
61e0a2b6dfSmrg * ReGIS:
622e4f8982Smrg * - ship a default alphabet zero font instead of scaling Xft fonts
63913cc679Smrg * - input cursors
64913cc679Smrg * - output cursors
65913cc679Smrg * - mouse/tablet/arrow-key input
662e4f8982Smrg * - fix graphic pages for ReGIS -- they should also apply to text and sixel graphics
6701037d57Smrg * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
6801037d57Smrg * - non-ASCII alphabets
692e4f8982Smrg * - enter/leave anywhere in a command
702e4f8982Smrg * - locator key definitions (DECLKD)
71894e0ac8Smrg * - command display mode
72913cc679Smrg * - re-rasterization on window resize
73894e0ac8Smrg * - macros
7401037d57Smrg * - improved fills for narrow angles (track actual lines not just pixels)
75913cc679Smrg * - hardcopy/screen-capture support (need dialog of some sort for safety)
76913cc679Smrg * - error reporting
7701037d57Smrg *
78e0a2b6dfSmrg * sixel:
79894e0ac8Smrg * - fix problem where new_row < 0 during sixel parsing (see FIXME)
8001037d57Smrg * - screen-capture support (need dialog of some sort for safety)
8101037d57Smrg *
82894e0ac8Smrg * VT55/VT105 waveform graphics
83894e0ac8Smrg * - everything
8401037d57Smrg *
8501037d57Smrg * Tektronix:
8601037d57Smrg * - color (VT340 4014 emulation, 41xx, IRAF GTERM, and also MS-DOS Kermit color support)
8701037d57Smrg * - polygon fill (41xx)
8801037d57Smrg * - clear area extension
8901037d57Smrg * - area fill extension
9001037d57Smrg * - pixel operations (RU/RS/RP)
9101037d57Smrg * - research other 41xx and 42xx extensions
9201037d57Smrg *
9301037d57Smrg * common graphics features:
94894e0ac8Smrg * - handle light/dark screen modes (CSI?5[hl])
95894e0ac8Smrg * - update text fg/bg color which overlaps images
9601037d57Smrg * - handle graphic updates in scroll regions (verify effect on graphics)
97894e0ac8Smrg * - handle rectangular area copies (verify they work with graphics)
9801037d57Smrg * - invalidate graphics under graphic if same origin, at least as big, and bg not transparent
9901037d57Smrg * - invalidate graphic if completely scrolled past end of scrollback
10001037d57Smrg * - invalidate graphic if all pixels are transparent/erased
10101037d57Smrg * - invalidate graphic if completely scrolled out of alt buffer
10201037d57Smrg * - posturize requested colors to match hardware palettes (e.g. only four possible shades on VT240)
103894e0ac8Smrg * - color register report/restore
10401037d57Smrg * - ability to select/copy graphics for pasting in other programs
10501037d57Smrg * - ability to show non-scroll-mode sixel graphics in a separate window
10601037d57Smrg * - ability to show ReGIS graphics in a separate window
10701037d57Smrg * - ability to show Tektronix graphics in VT100 window
1082e4f8982Smrg * - truncate graphics at bottom edge of terminal?
1092e4f8982Smrg * - locator events (DECEFR DECSLE DECELR DECLRP)
1102e4f8982Smrg * - locator controller mode (CSI6i / CSI7i)
11101037d57Smrg *
11201037d57Smrg * new escape sequences:
11301037d57Smrg * - way to query text font size without "window ops" (or make "window ops" permissions more fine grained)
114894e0ac8Smrg * - way to query and set the number of graphics pages
11501037d57Smrg *
116894e0ac8Smrg * ReGIS extensions:
11701037d57Smrg * - non-integer text scaling
118913cc679Smrg * - free distortionless text rotation (vs. simulating the distortion and aligning to 45deg increments)
11901037d57Smrg * - font characteristics: bold/underline/italic
12001037d57Smrg * - remove/increase arbitrary limits (pattern size, pages, alphabets, stack size, font names, etc.)
12101037d57Smrg * - shade/fill with borders
12201037d57Smrg * - sprites (copy portion of page into/out of buffer with scaling and rotation)
12301037d57Smrg * - ellipses
12401037d57Smrg * - 2D patterns
12501037d57Smrg * - option to set actual graphic size (not just coordinate range)
12601037d57Smrg * - gradients (for lines and fills)
127894e0ac8Smrg * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
128894e0ac8Smrg * - transparency
129894e0ac8Smrg * - background color as stackable write control
130894e0ac8Smrg * - true color (virtual color registers created upon lookup)
131894e0ac8Smrg * - anti-aliasing
132913cc679Smrg * - variable-width (proportional) text
133e0a2b6dfSmrg */
134e0a2b6dfSmrg
135e0a2b6dfSmrg/* font sizes:
136e0a2b6dfSmrg * VT510:
137e0a2b6dfSmrg *   80 Columns 132 Columns Maximum Number of Lines
138e0a2b6dfSmrg *   10 x 16   6 x 16  26 lines + keyboard indicator line
139894e0ac8Smrg *   10 x 13   6 x 13  26 lines + keyboard indicator line
140e0a2b6dfSmrg *   10 x 10   6 x 10  42 lines + keyboard indicator line
141e0a2b6dfSmrg *   10 x 8    6 x 8   53 lines + keyboard indicator line
14201037d57Smrg */
14301037d57Smrg
14401037d57Smrgtypedef struct allocated_color_register {
14501037d57Smrg    struct allocated_color_register *next;
14601037d57Smrg    Pixel pix;
14701037d57Smrg    short r, g, b;
14801037d57Smrg} AllocatedColorRegister;
14901037d57Smrg
15001037d57Smrg#define LOOKUP_WIDTH 16
15101037d57Smrgstatic AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
152e0a2b6dfSmrg
153894e0ac8Smrg#define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
154894e0ac8Smrg
155894e0ac8Smrgstatic ColorRegister *shared_color_registers;
156894e0ac8Smrgstatic Graphic *displayed_graphics[MAX_GRAPHICS];
157894e0ac8Smrgstatic unsigned next_graphic_id = 0U;
158f2e35a3aSmrgstatic unsigned used_graphics;	/* 0 to MAX_GRAPHICS */
159f2e35a3aSmrg
1604419d26bSmrgstatic int valid_graphics;
1614419d26bSmrgstatic GC graphics_gc;
1624419d26bSmrgstatic XGCValues xgcv;
1634419d26bSmrgstatic ColorRegister last_color;
1644419d26bSmrgstatic ColorRegister gc_color;
1654419d26bSmrg
166f2e35a3aSmrg#define DiffColor(this,that) \
167f2e35a3aSmrg	(this.r != that.r || \
168f2e35a3aSmrg	 this.g != that.g || \
169f2e35a3aSmrg	 this.b != that.b)
170f2e35a3aSmrg
171f2e35a3aSmrgstatic ColorRegister null_color =
172f2e35a3aSmrg{-1, -1, -1};
173894e0ac8Smrg
174894e0ac8Smrgstatic ColorRegister *
175894e0ac8SmrgallocRegisters(void)
176e0a2b6dfSmrg{
177894e0ac8Smrg    return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
178894e0ac8Smrg}
179894e0ac8Smrg
180894e0ac8Smrgstatic Graphic *
181894e0ac8SmrgfreeGraphic(Graphic *obj)
182894e0ac8Smrg{
183894e0ac8Smrg    if (obj) {
184f2e35a3aSmrg	free(obj->pixels);
185f2e35a3aSmrg	free(obj->private_color_registers);
186894e0ac8Smrg	free(obj);
187894e0ac8Smrg    }
188894e0ac8Smrg    return NULL;
189894e0ac8Smrg}
190894e0ac8Smrg
191894e0ac8Smrgstatic Graphic *
19201037d57SmrgallocGraphic(int max_w, int max_h)
193894e0ac8Smrg{
194894e0ac8Smrg    Graphic *result = TypeCalloc(Graphic);
195894e0ac8Smrg    if (result) {
19601037d57Smrg	result->max_width = max_w;
19701037d57Smrg	result->max_height = max_h;
19801037d57Smrg	if (!(result->pixels = TypeCallocN(RegisterNum,
19901037d57Smrg					     (size_t) max_w * (size_t) max_h))) {
200894e0ac8Smrg	    result = freeGraphic(result);
201894e0ac8Smrg	} else if (!(result->private_color_registers = allocRegisters())) {
202894e0ac8Smrg	    result = freeGraphic(result);
203e0a2b6dfSmrg	}
204e0a2b6dfSmrg    }
205894e0ac8Smrg    return result;
206894e0ac8Smrg}
207e0a2b6dfSmrg
208f2e35a3aSmrg#define getActiveSlot(n) \
209f2e35a3aSmrg	(((n) < MAX_GRAPHICS && \
210f2e35a3aSmrg	 displayed_graphics[n] && \
211f2e35a3aSmrg	 displayed_graphics[n]->valid) \
212f2e35a3aSmrg	 ? displayed_graphics[n] \
213f2e35a3aSmrg	 : NULL)
214e0a2b6dfSmrg
215894e0ac8Smrgstatic Graphic *
21601037d57SmrggetInactiveSlot(const TScreen *screen, unsigned n)
217894e0ac8Smrg{
218894e0ac8Smrg    if (n < MAX_GRAPHICS &&
219894e0ac8Smrg	(!displayed_graphics[n] ||
220894e0ac8Smrg	 !displayed_graphics[n]->valid)) {
221894e0ac8Smrg	if (!displayed_graphics[n]) {
22201037d57Smrg	    displayed_graphics[n] = allocGraphic(screen->graphics_max_wide,
22301037d57Smrg						 screen->graphics_max_high);
224f2e35a3aSmrg	    used_graphics += (displayed_graphics[n] != NULL);
225894e0ac8Smrg	}
226894e0ac8Smrg	return displayed_graphics[n];
227894e0ac8Smrg    }
228894e0ac8Smrg    return NULL;
229894e0ac8Smrg}
230894e0ac8Smrg
231894e0ac8Smrgstatic ColorRegister *
232894e0ac8SmrggetSharedRegisters(void)
233894e0ac8Smrg{
234894e0ac8Smrg    if (!shared_color_registers)
235894e0ac8Smrg	shared_color_registers = allocRegisters();
236894e0ac8Smrg    return shared_color_registers;
237894e0ac8Smrg}
238e0a2b6dfSmrg
239e0a2b6dfSmrgstatic void
240894e0ac8SmrgdeactivateSlot(unsigned n)
241e0a2b6dfSmrg{
242f2e35a3aSmrg    if ((n < MAX_GRAPHICS) && displayed_graphics[n]) {
243894e0ac8Smrg	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
244f2e35a3aSmrg	used_graphics--;
245894e0ac8Smrg    }
246894e0ac8Smrg}
247e0a2b6dfSmrg
248894e0ac8Smrgextern RegisterNum
249894e0ac8Smrgread_pixel(Graphic *graphic, int x, int y)
250894e0ac8Smrg{
2514419d26bSmrg    return (((x) >= 0 &&
2524419d26bSmrg	     (x) < (graphic)->actual_width &&
2534419d26bSmrg	     (y) >= 0 &&
2544419d26bSmrg	     (y) < (graphic)->actual_height)
2554419d26bSmrg	    ? (graphic)->pixels[(y) * (graphic)->max_width + (x)]
2564419d26bSmrg	    : (RegisterNum) COLOR_HOLE);
257e0a2b6dfSmrg}
258e0a2b6dfSmrg
25901037d57Smrg#define _draw_pixel(G, X, Y, C) \
26001037d57Smrg    do { \
2614419d26bSmrg        unsigned _cell = (unsigned)((Y) * (G)->max_width + (X)); \
2624419d26bSmrg        SetSpixel(G, _cell, (RegisterNum) (C)); \
26301037d57Smrg    } while (0)
26401037d57Smrg
265894e0ac8Smrgvoid
266894e0ac8Smrgdraw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
267e0a2b6dfSmrg{
268894e0ac8Smrg    assert(color <= MAX_COLOR_REGISTERS);
269e0a2b6dfSmrg
270894e0ac8Smrg#ifdef DEBUG_PIXEL
271894e0ac8Smrg    TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
272894e0ac8Smrg	   x,
273894e0ac8Smrg	   y,
274e0a2b6dfSmrg	   color,
275894e0ac8Smrg	   COLOR_HOLE,
276e0a2b6dfSmrg	   ((color != COLOR_HOLE)
277894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].r : 0U),
278e0a2b6dfSmrg	   ((color != COLOR_HOLE)
279894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].g : 0U),
280e0a2b6dfSmrg	   ((color != COLOR_HOLE)
281894e0ac8Smrg	    ? (unsigned) graphic->color_registers[color].b : 0U)));
282894e0ac8Smrg#endif
283894e0ac8Smrg    if (x >= 0 && x < graphic->actual_width &&
284894e0ac8Smrg	y >= 0 && y < graphic->actual_height) {
28501037d57Smrg	_draw_pixel(graphic, x, y, color);
286894e0ac8Smrg	if (color < MAX_COLOR_REGISTERS)
28704b94745Smrg	    graphic->color_registers_used[color] = True;
288894e0ac8Smrg    }
289894e0ac8Smrg}
290894e0ac8Smrg
291894e0ac8Smrgvoid
292894e0ac8Smrgdraw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
293894e0ac8Smrg{
294894e0ac8Smrg    int x, y;
295894e0ac8Smrg    int tmp;
296894e0ac8Smrg
297894e0ac8Smrg    assert(color <= MAX_COLOR_REGISTERS);
298894e0ac8Smrg
299894e0ac8Smrg    if (x1 > x2) {
300894e0ac8Smrg	EXCHANGE(x1, x2, tmp);
301894e0ac8Smrg    }
302894e0ac8Smrg    if (y1 > y2) {
303894e0ac8Smrg	EXCHANGE(y1, y2, tmp);
304894e0ac8Smrg    }
305894e0ac8Smrg
30601037d57Smrg    if (x2 < 0 || x1 >= graphic->actual_width ||
30701037d57Smrg	y2 < 0 || y1 >= graphic->actual_height)
30801037d57Smrg	return;
30901037d57Smrg
31001037d57Smrg    if (x1 < 0)
31101037d57Smrg	x1 = 0;
31201037d57Smrg    if (x2 >= graphic->actual_width)
31301037d57Smrg	x2 = graphic->actual_width - 1;
31401037d57Smrg    if (y1 < 0)
31501037d57Smrg	y1 = 0;
31601037d57Smrg    if (y2 >= graphic->actual_height)
31701037d57Smrg	y2 = graphic->actual_height - 1;
31801037d57Smrg
31901037d57Smrg    if (color < MAX_COLOR_REGISTERS)
32004b94745Smrg	graphic->color_registers_used[color] = True;
321894e0ac8Smrg    for (y = y1; y <= y2; y++)
32201037d57Smrg	for (x = x1; x <= x2; x++)
32301037d57Smrg	    _draw_pixel(graphic, x, y, color);
324894e0ac8Smrg}
325894e0ac8Smrg
32601037d57Smrgvoid
32701037d57Smrgcopy_overlapping_area(Graphic *graphic, int src_ul_x, int src_ul_y,
32801037d57Smrg		      int dst_ul_x, int dst_ul_y, unsigned w, unsigned h,
32901037d57Smrg		      unsigned default_color)
33001037d57Smrg{
33101037d57Smrg    int sx, ex, dx;
33201037d57Smrg    int sy, ey, dy;
33301037d57Smrg    int xx, yy;
33401037d57Smrg    RegisterNum color;
33501037d57Smrg
33601037d57Smrg    if (dst_ul_x <= src_ul_x) {
33701037d57Smrg	sx = 0;
33801037d57Smrg	ex = (int) w - 1;
33901037d57Smrg	dx = +1;
34001037d57Smrg    } else {
34101037d57Smrg	sx = (int) w - 1;
34201037d57Smrg	ex = 0;
34301037d57Smrg	dx = -1;
34401037d57Smrg    }
34501037d57Smrg
34601037d57Smrg    if (dst_ul_y <= src_ul_y) {
34701037d57Smrg	sy = 0;
34801037d57Smrg	ey = (int) h - 1;
34901037d57Smrg	dy = +1;
35001037d57Smrg    } else {
35101037d57Smrg	sy = (int) h - 1;
35201037d57Smrg	ey = 0;
35301037d57Smrg	dy = -1;
35401037d57Smrg    }
35501037d57Smrg
35601037d57Smrg    for (yy = sy; yy != ey + dy; yy += dy) {
3572e4f8982Smrg	int dst_y = dst_ul_y + yy;
3582e4f8982Smrg	int src_y = src_ul_y + yy;
35901037d57Smrg	if (dst_y < 0 || dst_y >= (int) graphic->actual_height)
36001037d57Smrg	    continue;
36101037d57Smrg
36201037d57Smrg	for (xx = sx; xx != ex + dx; xx += dx) {
3632e4f8982Smrg	    int dst_x = dst_ul_x + xx;
3642e4f8982Smrg	    int src_x = src_ul_x + xx;
3654419d26bSmrg	    int cell;
36601037d57Smrg	    if (dst_x < 0 || dst_x >= (int) graphic->actual_width)
36701037d57Smrg		continue;
36801037d57Smrg
36901037d57Smrg	    if (src_x < 0 || src_x >= (int) graphic->actual_width ||
37001037d57Smrg		src_y < 0 || src_y >= (int) graphic->actual_height)
37101037d57Smrg		color = (RegisterNum) default_color;
37201037d57Smrg	    else
37301037d57Smrg		color = graphic->pixels[(unsigned) (src_y *
37401037d57Smrg						    graphic->max_width) +
37501037d57Smrg					(unsigned) src_x];
37601037d57Smrg
3774419d26bSmrg	    cell = (int) ((unsigned) (dst_y * graphic->max_width) +
3784419d26bSmrg			  (unsigned) dst_x);
3794419d26bSmrg	    SetSpixel(graphic, cell, color);
38001037d57Smrg	}
38101037d57Smrg    }
38201037d57Smrg}
38301037d57Smrg
3844419d26bSmrg#define set_color_register(color_registers, color, pr, pg, pb) \
3854419d26bSmrgdo { \
38604b94745Smrg    assert(color <= MAX_COLOR_REGISTERS); \
38704b94745Smrg    { \
3884419d26bSmrg    ColorRegister *reg = &color_registers[color]; \
3894419d26bSmrg    reg->r = (short) pr; \
3904419d26bSmrg    reg->g = (short) pg; \
3914419d26bSmrg    reg->b = (short) pb; \
39204b94745Smrg    } \
3934419d26bSmrg} while (0)
394e0a2b6dfSmrg
395894e0ac8Smrg/* Graphics which don't use private colors will act as if they are using a
396894e0ac8Smrg * device-wide color palette.
397894e0ac8Smrg */
398894e0ac8Smrgstatic void
399894e0ac8Smrgset_shared_color_register(unsigned color, int r, int g, int b)
400894e0ac8Smrg{
401894e0ac8Smrg    unsigned ii;
402894e0ac8Smrg
403894e0ac8Smrg    assert(color < MAX_COLOR_REGISTERS);
404894e0ac8Smrg
405894e0ac8Smrg    set_color_register(getSharedRegisters(), color, r, g, b);
406894e0ac8Smrg
407f2e35a3aSmrg    if (!used_graphics)
408f2e35a3aSmrg	return;
409f2e35a3aSmrg
410894e0ac8Smrg    FOR_EACH_SLOT(ii) {
4112e4f8982Smrg	Graphic *graphic;
4122e4f8982Smrg
413894e0ac8Smrg	if (!(graphic = getActiveSlot(ii)))
414894e0ac8Smrg	    continue;
415894e0ac8Smrg	if (graphic->private_colors)
416894e0ac8Smrg	    continue;
417894e0ac8Smrg
418894e0ac8Smrg	if (graphic->color_registers_used[ii]) {
4194419d26bSmrg	    graphic->dirty = True;
420894e0ac8Smrg	}
421894e0ac8Smrg    }
422894e0ac8Smrg}
423894e0ac8Smrg
42404b94745Smrgvoid
42504b94745Smrgfetch_color_register(Graphic *graphic,
42604b94745Smrg		     unsigned color,
42704b94745Smrg		     ColorRegister *reg)
42804b94745Smrg{
42904b94745Smrg    assert(color < MAX_COLOR_REGISTERS);
43004b94745Smrg
43104b94745Smrg    reg->r = -1;
43204b94745Smrg    reg->g = -1;
43304b94745Smrg    reg->b = -1;
43404b94745Smrg
43504b94745Smrg    if (graphic->color_registers_used[color]) {
43604b94745Smrg	if (graphic->private_colors) {
43704b94745Smrg	    *reg = graphic->private_color_registers[color];
43804b94745Smrg	} else {
43904b94745Smrg	    *reg = getSharedRegisters()[color];
44004b94745Smrg	}
44104b94745Smrg    }
44204b94745Smrg}
44304b94745Smrg
444894e0ac8Smrgvoid
445894e0ac8Smrgupdate_color_register(Graphic *graphic,
446894e0ac8Smrg		      unsigned color,
447894e0ac8Smrg		      int r,
448894e0ac8Smrg		      int g,
449894e0ac8Smrg		      int b)
450894e0ac8Smrg{
451894e0ac8Smrg    assert(color < MAX_COLOR_REGISTERS);
452894e0ac8Smrg
453894e0ac8Smrg    if (graphic->private_colors) {
454894e0ac8Smrg	set_color_register(graphic->private_color_registers,
455894e0ac8Smrg			   color, r, g, b);
456894e0ac8Smrg	if (graphic->color_registers_used[color]) {
4574419d26bSmrg	    graphic->dirty = True;
458894e0ac8Smrg	}
45904b94745Smrg	graphic->color_registers_used[color] = True;
460894e0ac8Smrg    } else {
461894e0ac8Smrg	set_shared_color_register(color, r, g, b);
462894e0ac8Smrg    }
463894e0ac8Smrg}
464894e0ac8Smrg
465894e0ac8Smrg#define SQUARE(X) ( (X) * (X) )
466894e0ac8Smrg
467894e0ac8SmrgRegisterNum
468894e0ac8Smrgfind_color_register(ColorRegister const *color_registers, int r, int g, int b)
469894e0ac8Smrg{
470894e0ac8Smrg    unsigned i;
471894e0ac8Smrg    unsigned closest_index;
472894e0ac8Smrg    unsigned closest_distance;
473894e0ac8Smrg
474894e0ac8Smrg    /* I have no idea what algorithm DEC used for this.
475894e0ac8Smrg     * The documentation warns that it is unpredictable, especially with values
476894e0ac8Smrg     * far away from any allocated color so it is probably a very simple
47701037d57Smrg     * heuristic rather than something fancy like finding the minimum distance
478894e0ac8Smrg     * in a linear perceptive color space.
479894e0ac8Smrg     */
480894e0ac8Smrg    closest_index = MAX_COLOR_REGISTERS;
481894e0ac8Smrg    closest_distance = 0U;
482894e0ac8Smrg    for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
4832e4f8982Smrg	unsigned d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
4842e4f8982Smrg				 SQUARE(3 * (color_registers[i].g - g)) +
4852e4f8982Smrg				 SQUARE(1 * (color_registers[i].b - b)));
486894e0ac8Smrg	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
487894e0ac8Smrg	    closest_index = i;
488894e0ac8Smrg	    closest_distance = d;
489894e0ac8Smrg	}
490894e0ac8Smrg    }
491894e0ac8Smrg
492894e0ac8Smrg    TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
493894e0ac8Smrg	   r, g, b,
494894e0ac8Smrg	   closest_index,
495894e0ac8Smrg	   closest_distance,
496894e0ac8Smrg	   color_registers[closest_index].r,
497894e0ac8Smrg	   color_registers[closest_index].g,
498894e0ac8Smrg	   color_registers[closest_index].b));
499894e0ac8Smrg    return (RegisterNum) closest_index;
500894e0ac8Smrg}
501894e0ac8Smrg
50204b94745Smrg#if OPT_INHERIT_COLORS
503e0a2b6dfSmrgstatic void
50404b94745Smrgcopy_color_registers(Graphic *target, Graphic *source)
505e0a2b6dfSmrg{
50604b94745Smrg    memcpy(target->color_registers_used,
50704b94745Smrg	   source->color_registers_used,
50804b94745Smrg	   sizeof(Boolean) * MAX_COLOR_REGISTERS);
509e0a2b6dfSmrg
51004b94745Smrg    memcpy(target->private_color_registers,
51104b94745Smrg	   source->color_registers,
51204b94745Smrg	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
51304b94745Smrg}
51404b94745Smrg#endif
51504b94745Smrg
51604b94745Smrgstatic void
51704b94745Smrginit_color_registers(TScreen const *screen, ColorRegister *color_registers)
51804b94745Smrg{
51904b94745Smrg    const int graphics_termid = GraphicsTermId(screen);
52004b94745Smrg
52104b94745Smrg    TRACE(("setting initial colors for terminal %d\n", graphics_termid));
52204b94745Smrg    memset(color_registers,
52304b94745Smrg	   0,
52404b94745Smrg	   sizeof(ColorRegister) * MAX_COLOR_REGISTERS);
525e0a2b6dfSmrg
526e0a2b6dfSmrg    /*
527e0a2b6dfSmrg     * default color registers:
528e0a2b6dfSmrg     *     (mono) (color)
529e0a2b6dfSmrg     * VK100/GIGI (fixed)
530e0a2b6dfSmrg     * VT125:
531e0a2b6dfSmrg     *   0: 0%      0%
532e0a2b6dfSmrg     *   1: 33%     blue
533e0a2b6dfSmrg     *   2: 66%     red
534e0a2b6dfSmrg     *   3: 100%    green
535e0a2b6dfSmrg     * VT240:
536e0a2b6dfSmrg     *   0: 0%      0%
537e0a2b6dfSmrg     *   1: 33%     blue
538e0a2b6dfSmrg     *   2: 66%     red
539e0a2b6dfSmrg     *   3: 100%    green
540e0a2b6dfSmrg     * VT241:
541e0a2b6dfSmrg     *   0: 0%      0%
542e0a2b6dfSmrg     *   1: 33%     blue
543e0a2b6dfSmrg     *   2: 66%     red
544e0a2b6dfSmrg     *   3: 100%    green
545e0a2b6dfSmrg     * VT330:
546e0a2b6dfSmrg     *   0: 0%      0%              (bg for light on dark mode)
547e0a2b6dfSmrg     *   1: 33%     blue (red?)
548e0a2b6dfSmrg     *   2: 66%     red (green?)
549e0a2b6dfSmrg     *   3: 100%    green (yellow?) (fg for light on dark mode)
550e0a2b6dfSmrg     * VT340:
551e0a2b6dfSmrg     *   0: 0%      0%              (bg for light on dark mode)
552e0a2b6dfSmrg     *   1: 14%     blue
553e0a2b6dfSmrg     *   2: 29%     red
554e0a2b6dfSmrg     *   3: 43%     green
555e0a2b6dfSmrg     *   4: 57%     magenta
556e0a2b6dfSmrg     *   5: 71%     cyan
557e0a2b6dfSmrg     *   6: 86%     yellow
558e0a2b6dfSmrg     *   7: 100%    50%             (fg for light on dark mode)
559e0a2b6dfSmrg     *   8: 0%      25%
560e0a2b6dfSmrg     *   9: 14%     gray-blue
561e0a2b6dfSmrg     *  10: 29%     gray-red
562e0a2b6dfSmrg     *  11: 43%     gray-green
563e0a2b6dfSmrg     *  12: 57%     gray-magenta
564e0a2b6dfSmrg     *  13: 71%     gray-cyan
565e0a2b6dfSmrg     *  14: 86%     gray-yellow
566894e0ac8Smrg     *  15: 100%    75%             ("white")
567894e0ac8Smrg     * VT382:
568894e0ac8Smrg     *   ? (FIXME: B&W only?)
569e0a2b6dfSmrg     * dxterm:
570e0a2b6dfSmrg     *  ?
571e0a2b6dfSmrg     */
572f2e35a3aSmrg    switch (graphics_termid) {
573e0a2b6dfSmrg    case 125:
574e0a2b6dfSmrg    case 241:
575894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
576894e0ac8Smrg	set_color_register(color_registers, 1, 0, 0, 100);
577894e0ac8Smrg	set_color_register(color_registers, 2, 0, 100, 0);
578894e0ac8Smrg	set_color_register(color_registers, 3, 100, 0, 0);
579e0a2b6dfSmrg	break;
580e0a2b6dfSmrg    case 240:
581e0a2b6dfSmrg    case 330:
582894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
583894e0ac8Smrg	set_color_register(color_registers, 1, 33, 33, 33);
584894e0ac8Smrg	set_color_register(color_registers, 2, 66, 66, 66);
585894e0ac8Smrg	set_color_register(color_registers, 3, 100, 100, 100);
586e0a2b6dfSmrg	break;
587e0a2b6dfSmrg    case 340:
588e0a2b6dfSmrg    default:
589894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
590894e0ac8Smrg	set_color_register(color_registers, 1, 20, 20, 80);
591894e0ac8Smrg	set_color_register(color_registers, 2, 80, 13, 13);
592894e0ac8Smrg	set_color_register(color_registers, 3, 20, 80, 20);
593894e0ac8Smrg	set_color_register(color_registers, 4, 80, 20, 80);
594894e0ac8Smrg	set_color_register(color_registers, 5, 20, 80, 80);
595894e0ac8Smrg	set_color_register(color_registers, 6, 80, 80, 20);
596894e0ac8Smrg	set_color_register(color_registers, 7, 53, 53, 53);
597894e0ac8Smrg	set_color_register(color_registers, 8, 26, 26, 26);
598894e0ac8Smrg	set_color_register(color_registers, 9, 33, 33, 60);
599894e0ac8Smrg	set_color_register(color_registers, 10, 60, 26, 26);
600894e0ac8Smrg	set_color_register(color_registers, 11, 33, 60, 33);
601894e0ac8Smrg	set_color_register(color_registers, 12, 60, 33, 60);
602894e0ac8Smrg	set_color_register(color_registers, 13, 33, 60, 60);
603894e0ac8Smrg	set_color_register(color_registers, 14, 60, 60, 33);
604894e0ac8Smrg	set_color_register(color_registers, 15, 80, 80, 80);
605894e0ac8Smrg	break;
606894e0ac8Smrg    case 382:			/* FIXME: verify */
607894e0ac8Smrg	set_color_register(color_registers, 0, 0, 0, 0);
608894e0ac8Smrg	set_color_register(color_registers, 1, 100, 100, 100);
609e0a2b6dfSmrg	break;
610e0a2b6dfSmrg    }
611e0a2b6dfSmrg
612894e0ac8Smrg#ifdef DEBUG_PALETTE
613894e0ac8Smrg    {
614894e0ac8Smrg	unsigned i;
615e0a2b6dfSmrg
616894e0ac8Smrg	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
61701037d57Smrg	    TRACE(("initial value for register %03u: %d,%d,%d\n",
618894e0ac8Smrg		   i,
619894e0ac8Smrg		   color_registers[i].r,
620894e0ac8Smrg		   color_registers[i].g,
62101037d57Smrg		   color_registers[i].b));
622894e0ac8Smrg	}
623894e0ac8Smrg    }
624894e0ac8Smrg#endif
625894e0ac8Smrg}
626e0a2b6dfSmrg
627894e0ac8Smrgunsigned
628894e0ac8Smrgget_color_register_count(TScreen const *screen)
629894e0ac8Smrg{
630894e0ac8Smrg    unsigned num_color_registers;
631e0a2b6dfSmrg
632894e0ac8Smrg    if (screen->numcolorregisters >= 0) {
633894e0ac8Smrg	num_color_registers = (unsigned) screen->numcolorregisters;
634894e0ac8Smrg    } else {
635894e0ac8Smrg	num_color_registers = 0U;
636894e0ac8Smrg    }
637e0a2b6dfSmrg
638894e0ac8Smrg    if (num_color_registers > 1U) {
639894e0ac8Smrg	if (num_color_registers > MAX_COLOR_REGISTERS)
640894e0ac8Smrg	    return MAX_COLOR_REGISTERS;
641894e0ac8Smrg	return num_color_registers;
642894e0ac8Smrg    }
643e0a2b6dfSmrg
644e0a2b6dfSmrg    /*
645e0a2b6dfSmrg     * color capabilities:
646e0a2b6dfSmrg     * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
647e0a2b6dfSmrg     * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
648894e0ac8Smrg     * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
649e0a2b6dfSmrg     * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
650e0a2b6dfSmrg     * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
651e0a2b6dfSmrg     * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
652894e0ac8Smrg     * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
653e0a2b6dfSmrg     * dxterm      ?
654e0a2b6dfSmrg     */
655f2e35a3aSmrg    switch (screen->graphics_termid) {
656e0a2b6dfSmrg    case 125:
657894e0ac8Smrg	return 4U;
658e0a2b6dfSmrg    case 240:
659894e0ac8Smrg	return 4U;
660e0a2b6dfSmrg    case 241:
661894e0ac8Smrg	return 4U;
662e0a2b6dfSmrg    case 330:
663894e0ac8Smrg	return 4U;
664e0a2b6dfSmrg    case 340:
665894e0ac8Smrg	return 16U;
666894e0ac8Smrg    case 382:
667894e0ac8Smrg	return 2U;
668e0a2b6dfSmrg    default:
669894e0ac8Smrg	/* unknown graphics model -- might as well be generous */
670894e0ac8Smrg	return MAX_COLOR_REGISTERS;
671e0a2b6dfSmrg    }
672894e0ac8Smrg}
673894e0ac8Smrg
674894e0ac8Smrgstatic void
67504b94745Smrginit_graphic(TScreen *screen,
67604b94745Smrg	     Graphic *graphic,
677894e0ac8Smrg	     unsigned type,
678894e0ac8Smrg	     int charrow,
679894e0ac8Smrg	     int charcol,
68004b94745Smrg	     unsigned num_color_registers)
681894e0ac8Smrg{
68204b94745Smrg    int private_colors = screen->privatecolorregisters;
68301037d57Smrg    const unsigned max_pixels = (unsigned) (graphic->max_width *
68401037d57Smrg					    graphic->max_height);
685894e0ac8Smrg
6864419d26bSmrg    TRACE(("init_graphic %u pixels at %d,%d\n", max_pixels, charrow, charcol));
687894e0ac8Smrg
6884419d26bSmrg    graphic->hidden = False;
6894419d26bSmrg    graphic->dirty = True;
6904419d26bSmrg    memset(graphic->pixels, COLOR_HOLE & 0xff, max_pixels * sizeof(RegisterNum));
69104b94745Smrg    memset(graphic->color_registers_used, False, sizeof(graphic->color_registers_used));
692e0a2b6dfSmrg
693e0a2b6dfSmrg    /*
694e0a2b6dfSmrg     * text and graphics interactions:
695e0a2b6dfSmrg     * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
696e0a2b6dfSmrg     * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
697894e0ac8Smrg     * VT382                     text writes on top of graphics buffer FIXME: verify
698e0a2b6dfSmrg     * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
699e0a2b6dfSmrg     */
700e0a2b6dfSmrg
701894e0ac8Smrg    /*
702894e0ac8Smrg     * dimensions (ReGIS logical, physical):
703894e0ac8Smrg     * VK100/GIGI  768x4??  768x240(status?)
704894e0ac8Smrg     * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
705894e0ac8Smrg     * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
706894e0ac8Smrg     * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
707894e0ac8Smrg     * VT330       800x480  800x480(+?status)
708894e0ac8Smrg     * VT340       800x480  800x480(+?status)
709894e0ac8Smrg     * VT382       960x750  sixel only
710894e0ac8Smrg     * dxterm      ?x? ?x?  variable?
711894e0ac8Smrg     */
712e0a2b6dfSmrg
713e0a2b6dfSmrg    graphic->actual_width = 0;
714e0a2b6dfSmrg    graphic->actual_height = 0;
715e0a2b6dfSmrg
716894e0ac8Smrg    graphic->pixw = 1;
717894e0ac8Smrg    graphic->pixh = 1;
718894e0ac8Smrg
719894e0ac8Smrg    graphic->valid_registers = num_color_registers;
720894e0ac8Smrg    TRACE(("%d color registers\n", graphic->valid_registers));
721894e0ac8Smrg
722e0a2b6dfSmrg    graphic->private_colors = private_colors;
723e0a2b6dfSmrg    if (graphic->private_colors) {
72404b94745Smrg#if OPT_INHERIT_COLORS
72504b94745Smrg	unsigned ii;
72604b94745Smrg	int max_charrow = -1;
72704b94745Smrg	Graphic *newest = NULL;
72804b94745Smrg#endif
72904b94745Smrg
730894e0ac8Smrg	TRACE(("using private color registers\n"));
73104b94745Smrg
73204b94745Smrg#if OPT_INHERIT_COLORS
73304b94745Smrg	FOR_EACH_SLOT(ii) {
73404b94745Smrg	    Graphic *check;
73504b94745Smrg	    if (!(check = getActiveSlot(ii)))
73604b94745Smrg		continue;
73704b94745Smrg	    if (!newest || check->charrow >= max_charrow) {
73804b94745Smrg		max_charrow = check->charrow;
73904b94745Smrg		newest = check;
74004b94745Smrg	    }
74104b94745Smrg	}
74204b94745Smrg
74304b94745Smrg	if (newest != NULL && newest != graphic) {
74404b94745Smrg	    copy_color_registers(graphic, newest);
74504b94745Smrg	} else {
74604b94745Smrg	    init_color_registers(screen, graphic->private_color_registers);
74704b94745Smrg	}
74804b94745Smrg#else
74904b94745Smrg	init_color_registers(screen, graphic->private_color_registers);
75004b94745Smrg#endif
751e0a2b6dfSmrg	graphic->color_registers = graphic->private_color_registers;
752e0a2b6dfSmrg    } else {
753894e0ac8Smrg	TRACE(("using shared color registers\n"));
754894e0ac8Smrg	graphic->color_registers = getSharedRegisters();
755e0a2b6dfSmrg    }
756e0a2b6dfSmrg
757894e0ac8Smrg    graphic->charrow = charrow;
758894e0ac8Smrg    graphic->charcol = charcol;
759894e0ac8Smrg    graphic->type = type;
7604419d26bSmrg    graphic->valid = False;
761e0a2b6dfSmrg}
762e0a2b6dfSmrg
763894e0ac8SmrgGraphic *
764894e0ac8Smrgget_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
765e0a2b6dfSmrg{
76604b94745Smrg    TScreen *screen = TScreenOf(xw);
7674419d26bSmrg    const int bufferid = screen->whichBuf;
768f2e35a3aSmrg    Graphic *graphic = NULL;
769894e0ac8Smrg    unsigned ii;
770e0a2b6dfSmrg
77104b94745Smrg    TRACE(("get_new_graphic %d,%d type %d\n", charrow, charcol, type));
77204b94745Smrg
773894e0ac8Smrg    FOR_EACH_SLOT(ii) {
7745104ee6eSmrg	if ((graphic = getInactiveSlot(screen, ii)) != NULL) {
77504b94745Smrg	    TRACE(("using fresh graphic index %u as id %u\n",
77604b94745Smrg		   ii, next_graphic_id));
777e0a2b6dfSmrg	    break;
778894e0ac8Smrg	}
779e0a2b6dfSmrg    }
780e0a2b6dfSmrg
781894e0ac8Smrg    /* if none are free, recycle the graphic scrolled back the farthest */
782894e0ac8Smrg    if (!graphic) {
783e0a2b6dfSmrg	int min_charrow = 0;
784894e0ac8Smrg	Graphic *min_graphic = NULL;
78504b94745Smrg	if_TRACE(unsigned best_ii = (1 + MAX_GRAPHICS));
786e0a2b6dfSmrg
787894e0ac8Smrg	FOR_EACH_SLOT(ii) {
788894e0ac8Smrg	    if (!(graphic = getActiveSlot(ii)))
789894e0ac8Smrg		continue;
790e0a2b6dfSmrg	    if (!min_graphic || graphic->charrow < min_charrow) {
79104b94745Smrg		if_TRACE(best_ii = ii);
792e0a2b6dfSmrg		min_charrow = graphic->charrow;
793e0a2b6dfSmrg		min_graphic = graphic;
794e0a2b6dfSmrg	    }
795e0a2b6dfSmrg	}
79604b94745Smrg	TRACE(("recycling old graphic index %u as id %u\n",
79704b94745Smrg	       best_ii, next_graphic_id));
798e0a2b6dfSmrg	graphic = min_graphic;
799e0a2b6dfSmrg    }
800e0a2b6dfSmrg
801894e0ac8Smrg    if (graphic) {
802894e0ac8Smrg	unsigned num_color_registers;
803894e0ac8Smrg	num_color_registers = get_color_register_count(screen);
804894e0ac8Smrg	graphic->xw = xw;
805894e0ac8Smrg	graphic->bufferid = bufferid;
806894e0ac8Smrg	graphic->id = next_graphic_id++;
80704b94745Smrg	init_graphic(screen,
80804b94745Smrg		     graphic,
809894e0ac8Smrg		     type,
810894e0ac8Smrg		     charrow,
811894e0ac8Smrg		     charcol,
81204b94745Smrg		     num_color_registers);
813894e0ac8Smrg    }
814e0a2b6dfSmrg    return graphic;
815e0a2b6dfSmrg}
816e0a2b6dfSmrg
817894e0ac8SmrgGraphic *
818894e0ac8Smrgget_new_or_matching_graphic(XtermWidget xw,
819894e0ac8Smrg			    int charrow,
820894e0ac8Smrg			    int charcol,
821894e0ac8Smrg			    int actual_width,
822894e0ac8Smrg			    int actual_height,
823894e0ac8Smrg			    unsigned type)
824e0a2b6dfSmrg{
825894e0ac8Smrg    TScreen const *screen = TScreenOf(xw);
8264419d26bSmrg    const int bufferid = screen->whichBuf;
827894e0ac8Smrg    Graphic *graphic;
828894e0ac8Smrg    unsigned ii;
829894e0ac8Smrg
830894e0ac8Smrg    FOR_EACH_SLOT(ii) {
8312e4f8982Smrg	TRACE(("checking slot=%u for graphic at %d,%d %dx%d bufferid=%d type=%u\n", ii,
8322e4f8982Smrg	       charrow, charcol,
8332e4f8982Smrg	       actual_width, actual_height,
8342e4f8982Smrg	       bufferid, type));
8355104ee6eSmrg	if ((graphic = getActiveSlot(ii)) != NULL) {
8362e4f8982Smrg	    if (graphic->type == type &&
8372e4f8982Smrg		graphic->bufferid == bufferid &&
8382e4f8982Smrg		graphic->charrow == charrow &&
8392e4f8982Smrg		graphic->charcol == charcol &&
8402e4f8982Smrg		graphic->actual_width == actual_width &&
8412e4f8982Smrg		graphic->actual_height == actual_height) {
8422e4f8982Smrg		TRACE(("found existing graphic slot=%u id=%u\n", ii, graphic->id));
8432e4f8982Smrg		return graphic;
8442e4f8982Smrg	    }
8452e4f8982Smrg	    TRACE(("not a match: graphic at %d,%d %dx%d bufferid=%d type=%u\n",
8462e4f8982Smrg		   graphic->charrow, graphic->charcol,
8472e4f8982Smrg		   graphic->actual_width, graphic->actual_height,
8482e4f8982Smrg		   graphic->bufferid, graphic->type));
849e0a2b6dfSmrg	}
850e0a2b6dfSmrg    }
851e0a2b6dfSmrg
852894e0ac8Smrg    /* if no match get a new graphic */
8535104ee6eSmrg    if ((graphic = get_new_graphic(xw, charrow, charcol, type)) != NULL) {
854894e0ac8Smrg	graphic->actual_width = actual_width;
855894e0ac8Smrg	graphic->actual_height = actual_height;
8562e4f8982Smrg	TRACE(("no match; created graphic at %d,%d %dx%d bufferid=%d type=%u\n",
8572e4f8982Smrg	       graphic->charrow, graphic->charcol,
8582e4f8982Smrg	       graphic->actual_width, graphic->actual_height,
8592e4f8982Smrg	       graphic->bufferid, graphic->type));
860e0a2b6dfSmrg    }
861894e0ac8Smrg    return graphic;
862e0a2b6dfSmrg}
863e0a2b6dfSmrg
864a5ae21e4Smrg#define ScaleForXColor(s) (unsigned short) ((unsigned long)(s) * MAX_U_COLOR / CHANNEL_MAX)
86501037d57Smrg
86601037d57Smrgstatic int
86701037d57Smrgsave_allocated_color(const ColorRegister *reg, XtermWidget xw, Pixel *pix)
86801037d57Smrg{
86901037d57Smrg    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
87001037d57Smrg    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
87101037d57Smrg    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
87201037d57Smrg    XColor xcolor;
87301037d57Smrg    AllocatedColorRegister *new_color;
87401037d57Smrg
8754419d26bSmrg    /* *INDENT-EQLS* */
87601037d57Smrg    xcolor.pixel = 0UL;
8774419d26bSmrg    xcolor.red   = ScaleForXColor(reg->r);
87801037d57Smrg    xcolor.green = ScaleForXColor(reg->g);
8794419d26bSmrg    xcolor.blue  = ScaleForXColor(reg->b);
88001037d57Smrg    xcolor.flags = DoRed | DoGreen | DoBlue;
8814419d26bSmrg
88201037d57Smrg    if (!allocateBestRGB(xw, &xcolor)) {
88301037d57Smrg	TRACE(("unable to allocate xcolor\n"));
88401037d57Smrg	*pix = 0UL;
88501037d57Smrg	return 0;
886f2e35a3aSmrg    } else {
887f2e35a3aSmrg	*pix = xcolor.pixel;
88801037d57Smrg
8895307cd1aSmrg	if (!(new_color = TypeMalloc(AllocatedColorRegister))) {
890f2e35a3aSmrg	    TRACE(("unable to save pixel %lu\n", (unsigned long) *pix));
891f2e35a3aSmrg	    return 0;
892f2e35a3aSmrg	} else {
893f2e35a3aSmrg	    new_color->r = reg->r;
894f2e35a3aSmrg	    new_color->g = reg->g;
895f2e35a3aSmrg	    new_color->b = reg->b;
896f2e35a3aSmrg	    new_color->pix = *pix;
897f2e35a3aSmrg	    new_color->next = allocated_colors[rr][gg][bb];
89801037d57Smrg
899f2e35a3aSmrg	    allocated_colors[rr][gg][bb] = new_color;
90001037d57Smrg
901f2e35a3aSmrg	    return 1;
902f2e35a3aSmrg	}
903f2e35a3aSmrg    }
90401037d57Smrg}
90501037d57Smrg
906f2e35a3aSmrg/* FIXME: with so many possible colors we need to determine
907f2e35a3aSmrg * when to free them to be nice to PseudoColor displays
908f2e35a3aSmrg */
90901037d57Smrgstatic Pixel
91001037d57Smrgcolor_register_to_xpixel(const ColorRegister *reg, XtermWidget xw)
91101037d57Smrg{
912f2e35a3aSmrg    Pixel result;
913f2e35a3aSmrg    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
914f2e35a3aSmrg    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
915f2e35a3aSmrg    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
916f2e35a3aSmrg    const AllocatedColorRegister *search;
91701037d57Smrg
918f2e35a3aSmrg    for (search = allocated_colors[rr][gg][bb]; search; search = search->next) {
919f2e35a3aSmrg	if (search->r == reg->r &&
920f2e35a3aSmrg	    search->g == reg->g &&
921f2e35a3aSmrg	    search->b == reg->b) {
922f2e35a3aSmrg	    return search->pix;
923f2e35a3aSmrg	}
924f2e35a3aSmrg    }
92501037d57Smrg
926f2e35a3aSmrg    save_allocated_color(reg, xw, &result);
927f2e35a3aSmrg    return result;
928e0a2b6dfSmrg}
929e0a2b6dfSmrg
930e0a2b6dfSmrgstatic void
931894e0ac8Smrgrefresh_graphic(TScreen const *screen,
932894e0ac8Smrg		Graphic const *graphic,
93301037d57Smrg		ColorRegister *buffer,
93401037d57Smrg		int refresh_x,
93501037d57Smrg		int refresh_y,
93601037d57Smrg		int refresh_w,
93701037d57Smrg		int refresh_h,
93801037d57Smrg		int draw_x,
93901037d57Smrg		int draw_y,
94001037d57Smrg		int draw_w,
94101037d57Smrg		int draw_h)
942e0a2b6dfSmrg{
94301037d57Smrg    int const pw = graphic->pixw;
94401037d57Smrg    int const ph = graphic->pixh;
94501037d57Smrg    int const graph_x = graphic->charcol * FontWidth(screen);
94601037d57Smrg    int const graph_y = graphic->charrow * FontHeight(screen);
94701037d57Smrg    int const graph_w = graphic->actual_width;
94801037d57Smrg    int const graph_h = graphic->actual_height;
94901037d57Smrg    int const mw = graphic->max_width;
9504419d26bSmrg
951e0a2b6dfSmrg    int r, c;
9524419d26bSmrg    int pmy;
95301037d57Smrg    RegisterNum regnum;
954e0a2b6dfSmrg
9554419d26bSmrg    if_TRACE(int holes = 0);
9564419d26bSmrg    if_TRACE(int total = 0);
9574419d26bSmrg    if_TRACE(int out_of_range = 0);
9584419d26bSmrg
95901037d57Smrg    TRACE(("refreshing graphic %u from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d)\n",
96001037d57Smrg	   graphic->id,
96101037d57Smrg	   graph_x, graph_y, draw_w, draw_h,
962e0a2b6dfSmrg	   graphic->valid,
963e0a2b6dfSmrg	   graphic->actual_width,
964e0a2b6dfSmrg	   graphic->actual_height,
96501037d57Smrg	   pw, ph,
966e0a2b6dfSmrg	   graphic->max_width,
96701037d57Smrg	   graphic->max_height));
968e0a2b6dfSmrg
96901037d57Smrg    TRACE(("refresh pixmap starts at %d,%d\n", refresh_x, refresh_y));
970e0a2b6dfSmrg
9714419d26bSmrg    for (r = 0, pmy = graph_y; r < graph_h; r++, pmy += ph) {
9724419d26bSmrg	int pmx, buffer_y, pixel_y;
973894e0ac8Smrg
97401037d57Smrg	if (pmy + ph - 1 < draw_y)
975894e0ac8Smrg	    continue;
97601037d57Smrg	if (pmy > draw_y + draw_h - 1)
97701037d57Smrg	    break;
978894e0ac8Smrg
9794419d26bSmrg	if (pmy < draw_y ||
9804419d26bSmrg	    pmy < refresh_y ||
9814419d26bSmrg	    pmy > refresh_y + refresh_h - 1) {
9824419d26bSmrg	    if_TRACE(out_of_range++);
983f2e35a3aSmrg	    continue;
984f2e35a3aSmrg	}
9854419d26bSmrg	pixel_y = r * mw;
9864419d26bSmrg	buffer_y = (pmy - refresh_y) * refresh_w;
987f2e35a3aSmrg
9884419d26bSmrg	for (c = 0, pmx = graph_x; c < graph_w; c++, pmx += pw) {
989e0a2b6dfSmrg
99001037d57Smrg	    if (pmx + pw - 1 < draw_x)
991894e0ac8Smrg		continue;
99201037d57Smrg	    if (pmx > draw_x + draw_w - 1)
99301037d57Smrg		break;
994e0a2b6dfSmrg
9954419d26bSmrg	    if (pmx < draw_x ||
9964419d26bSmrg		pmx < refresh_x ||
9974419d26bSmrg		pmx > refresh_x + refresh_w - 1) {
9984419d26bSmrg		if_TRACE(out_of_range++);
999f2e35a3aSmrg		continue;
1000f2e35a3aSmrg	    }
1001f2e35a3aSmrg
10024419d26bSmrg	    if_TRACE(total++);
10034419d26bSmrg	    regnum = graphic->pixels[pixel_y + c];
100401037d57Smrg	    if (regnum == COLOR_HOLE) {
10054419d26bSmrg		if_TRACE(holes++);
1006f2e35a3aSmrg	    } else {
10074419d26bSmrg		buffer[buffer_y + (pmx - refresh_x)] =
1008f2e35a3aSmrg		    graphic->color_registers[regnum];
1009e0a2b6dfSmrg	    }
1010e0a2b6dfSmrg	}
1011894e0ac8Smrg    }
1012e0a2b6dfSmrg
101301037d57Smrg    TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes; %d were out of pixmap range\n",
101401037d57Smrg	   holes, total, out_of_range));
101501037d57Smrg}
101601037d57Smrg
101704b94745Smrg#define MAX_PCT 100.		/* HLS uses this for L, S percentages */
101804b94745Smrg#define MAX_RGB 100.		/* use this rather than 255 */
101904b94745Smrg
1020e0a2b6dfSmrg/*
102104b94745Smrg * In HLS, H is an angle, in degrees, and L, S are percentages.
1022e0a2b6dfSmrg * Primary color hues:
1023e0a2b6dfSmrg *  blue:    0 degrees
1024e0a2b6dfSmrg *  red:   120 degrees
1025e0a2b6dfSmrg *  green: 240 degrees
1026e0a2b6dfSmrg */
1027894e0ac8Smrgvoid
1028e0a2b6dfSmrghls2rgb(int h, int l, int s, short *r, short *g, short *b)
1029e0a2b6dfSmrg{
103004b94745Smrg    int hs;
103104b94745Smrg    const double lv = l / MAX_PCT;
103204b94745Smrg    const double sv = s / MAX_PCT;
1033894e0ac8Smrg    double c, x, m, c2;
1034e0a2b6dfSmrg    double r1, g1, b1;
1035e0a2b6dfSmrg
103604b94745Smrg    h = h - 120;		/* Rotate so that blue is at 0 degrees  */
103704b94745Smrg    while (h < 0)
103804b94745Smrg	h += 360;		/* Normalize to 0 to 360, */
103904b94745Smrg    while (h >= 360)
104004b94745Smrg	h -= 360;
104104b94745Smrg    hs = ((h + 59) / 60) % 6;
104204b94745Smrg
1043e0a2b6dfSmrg    if (s == 0) {
1044e0a2b6dfSmrg	*r = *g = *b = (short) l;
1045e0a2b6dfSmrg	return;
1046e0a2b6dfSmrg    }
1047e0a2b6dfSmrg
104801037d57Smrg    c2 = (2.0 * lv) - 1.0;
104901037d57Smrg    if (c2 < 0.0)
1050894e0ac8Smrg	c2 = -c2;
1051894e0ac8Smrg    c = (1.0 - c2) * sv;
105201037d57Smrg    x = (hs & 1) ? c : 0.0;
1053e0a2b6dfSmrg    m = lv - 0.5 * c;
1054e0a2b6dfSmrg
105501037d57Smrg    switch (hs) {
1056e0a2b6dfSmrg    case 0:
1057e0a2b6dfSmrg	r1 = c;
1058e0a2b6dfSmrg	g1 = x;
1059e0a2b6dfSmrg	b1 = 0.0;
1060e0a2b6dfSmrg	break;
1061e0a2b6dfSmrg    case 1:
1062e0a2b6dfSmrg	r1 = x;
1063e0a2b6dfSmrg	g1 = c;
1064e0a2b6dfSmrg	b1 = 0.0;
1065e0a2b6dfSmrg	break;
1066e0a2b6dfSmrg    case 2:
1067e0a2b6dfSmrg	r1 = 0.0;
1068e0a2b6dfSmrg	g1 = c;
1069e0a2b6dfSmrg	b1 = x;
1070e0a2b6dfSmrg	break;
1071e0a2b6dfSmrg    case 3:
1072e0a2b6dfSmrg	r1 = 0.0;
1073e0a2b6dfSmrg	g1 = x;
1074e0a2b6dfSmrg	b1 = c;
1075e0a2b6dfSmrg	break;
1076e0a2b6dfSmrg    case 4:
1077e0a2b6dfSmrg	r1 = x;
1078e0a2b6dfSmrg	g1 = 0.0;
1079e0a2b6dfSmrg	b1 = c;
1080e0a2b6dfSmrg	break;
1081e0a2b6dfSmrg    case 5:
1082e0a2b6dfSmrg	r1 = c;
1083e0a2b6dfSmrg	g1 = 0.0;
1084e0a2b6dfSmrg	b1 = x;
1085e0a2b6dfSmrg	break;
1086e0a2b6dfSmrg    default:
1087894e0ac8Smrg	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
108804b94745Smrg	*r = (short) 360;
108904b94745Smrg	*g = (short) MAX_PCT;
109004b94745Smrg	*b = (short) MAX_PCT;
1091e0a2b6dfSmrg	return;
1092e0a2b6dfSmrg    }
1093e0a2b6dfSmrg
109404b94745Smrg    *r = (short) ((r1 + m) * MAX_PCT + 0.5);
109504b94745Smrg    *g = (short) ((g1 + m) * MAX_PCT + 0.5);
109604b94745Smrg    *b = (short) ((b1 + m) * MAX_PCT + 0.5);
1097e0a2b6dfSmrg
1098e0a2b6dfSmrg    if (*r < 0)
1099e0a2b6dfSmrg	*r = 0;
1100e0a2b6dfSmrg    else if (*r > 100)
1101e0a2b6dfSmrg	*r = 100;
1102e0a2b6dfSmrg    if (*g < 0)
1103e0a2b6dfSmrg	*g = 0;
1104e0a2b6dfSmrg    else if (*g > 100)
1105e0a2b6dfSmrg	*g = 100;
1106e0a2b6dfSmrg    if (*b < 0)
1107e0a2b6dfSmrg	*b = 0;
1108e0a2b6dfSmrg    else if (*b > 100)
1109e0a2b6dfSmrg	*b = 100;
1110e0a2b6dfSmrg}
1111e0a2b6dfSmrg
111204b94745Smrgvoid
111304b94745Smrgrgb2hls(int r, int g, int b, short *h, short *l, short *s)
111404b94745Smrg{
111504b94745Smrg    const double scaled_r = (r / MAX_RGB);
111604b94745Smrg    const double scaled_g = (g / MAX_RGB);
111704b94745Smrg    const double scaled_b = (b / MAX_RGB);
111804b94745Smrg
111904b94745Smrg    const double min_scale = Min(Min(scaled_r, scaled_g), scaled_b);
112004b94745Smrg    const double max_scale = Max(Max(scaled_r, scaled_g), scaled_b);
112104b94745Smrg    const double dif_scale = max_scale - min_scale;
112204b94745Smrg
112304b94745Smrg    double h_work = 0.;
112404b94745Smrg    double s_work = 0.;
112504b94745Smrg    double l_work = ((max_scale + min_scale) / 2.);
112604b94745Smrg
112704b94745Smrg    if (dif_scale != 0.) {
112804b94745Smrg	if (l_work < 0.5f) {
112904b94745Smrg	    s_work = (dif_scale / (max_scale + min_scale));
113004b94745Smrg	} else {
113104b94745Smrg	    s_work = (dif_scale / (2. - max_scale - min_scale));
113204b94745Smrg	}
113304b94745Smrg
113404b94745Smrg	if (scaled_r == max_scale) {
113504b94745Smrg	    h_work = (scaled_g - scaled_b) / dif_scale;
113604b94745Smrg	} else if (scaled_g == max_scale) {
113704b94745Smrg	    h_work = 2. + (scaled_b - scaled_r) / dif_scale;
113804b94745Smrg	} else if (scaled_b == max_scale) {
113904b94745Smrg	    h_work = 4. + (scaled_r - scaled_g) / dif_scale;
114004b94745Smrg	}
114104b94745Smrg    }
114204b94745Smrg
114304b94745Smrg    h_work *= 60.;
114404b94745Smrg    if (h_work < 0)
114504b94745Smrg	h_work += 360.;
114604b94745Smrg
114704b94745Smrg    s_work *= MAX_RGB;
114804b94745Smrg    l_work *= MAX_RGB;
114904b94745Smrg
115004b94745Smrg    *h = (short) h_work;
115104b94745Smrg    *s = (short) s_work;
115204b94745Smrg    *l = (short) l_work;
115304b94745Smrg}
115404b94745Smrg
1155894e0ac8Smrgvoid
1156894e0ac8Smrgdump_graphic(Graphic const *graphic)
1157e0a2b6dfSmrg{
1158894e0ac8Smrg#if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
1159894e0ac8Smrg    RegisterNum color;
1160894e0ac8Smrg#endif
1161894e0ac8Smrg#ifdef DUMP_BITMAP
1162894e0ac8Smrg    int r, c;
1163894e0ac8Smrg    ColorRegister const *reg;
1164894e0ac8Smrg#endif
1165e0a2b6dfSmrg
1166894e0ac8Smrg    (void) graphic;
1167e0a2b6dfSmrg
1168894e0ac8Smrg    TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
1169894e0ac8Smrg	   graphic->id,
1170894e0ac8Smrg	   graphic->charrow,
1171894e0ac8Smrg	   graphic->charcol,
1172894e0ac8Smrg	   graphic->actual_width,
1173894e0ac8Smrg	   graphic->actual_height,
1174894e0ac8Smrg	   graphic->pixw,
1175894e0ac8Smrg	   graphic->pixh));
1176e0a2b6dfSmrg
1177894e0ac8Smrg#ifdef DUMP_COLORS
1178894e0ac8Smrg    TRACE(("graphic colors:\n"));
1179894e0ac8Smrg    for (color = 0; color < graphic->valid_registers; color++) {
1180894e0ac8Smrg	TRACE(("%03u: %d,%d,%d\n",
1181894e0ac8Smrg	       color,
1182894e0ac8Smrg	       graphic->color_registers[color].r,
1183894e0ac8Smrg	       graphic->color_registers[color].g,
1184894e0ac8Smrg	       graphic->color_registers[color].b));
1185e0a2b6dfSmrg    }
1186e0a2b6dfSmrg#endif
1187e0a2b6dfSmrg
1188894e0ac8Smrg#ifdef DUMP_BITMAP
1189894e0ac8Smrg    TRACE(("graphic pixels:\n"));
1190894e0ac8Smrg    for (r = 0; r < graphic->actual_height; r++) {
1191894e0ac8Smrg	for (c = 0; c < graphic->actual_width; c++) {
1192894e0ac8Smrg	    color = graphic->pixels[r * graphic->max_width + c];
1193894e0ac8Smrg	    if (color == COLOR_HOLE) {
1194894e0ac8Smrg		TRACE(("?"));
1195e0a2b6dfSmrg	    } else {
1196894e0ac8Smrg		reg = &graphic->color_registers[color];
1197894e0ac8Smrg		if (reg->r + reg->g + reg->b > 200) {
1198894e0ac8Smrg		    TRACE(("#"));
1199894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 150) {
1200894e0ac8Smrg		    TRACE(("%%"));
1201894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 100) {
1202894e0ac8Smrg		    TRACE((":"));
1203894e0ac8Smrg		} else if (reg->r + reg->g + reg->b > 80) {
1204894e0ac8Smrg		    TRACE(("."));
1205894e0ac8Smrg		} else {
1206894e0ac8Smrg		    TRACE((" "));
1207e0a2b6dfSmrg		}
1208e0a2b6dfSmrg	    }
1209e0a2b6dfSmrg	}
1210894e0ac8Smrg	TRACE(("\n"));
1211e0a2b6dfSmrg    }
1212e0a2b6dfSmrg
1213894e0ac8Smrg    TRACE(("\n"));
1214894e0ac8Smrg#endif
1215e0a2b6dfSmrg}
1216e0a2b6dfSmrg
1217e0a2b6dfSmrg/* Erase the portion of any displayed graphic overlapping with a rectangle
121801037d57Smrg * of the given size and location in pixels relative to the start of the
121901037d57Smrg * graphic.  This is used to allow text to "erase" graphics underneath it.
1220e0a2b6dfSmrg */
1221e0a2b6dfSmrgstatic void
1222894e0ac8Smrgerase_graphic(Graphic *graphic, int x, int y, int w, int h)
1223e0a2b6dfSmrg{
12244419d26bSmrg    const int pw = graphic->pixw;
12254419d26bSmrg    const int ph = graphic->pixh;
12264419d26bSmrg    const int r_min = y - ph + 1;
12274419d26bSmrg    const int r_max = y + h - 1;
12284419d26bSmrg    const int c_min = x - pw + 1;
12294419d26bSmrg    const int c_max = x + w - 1;
1230e0a2b6dfSmrg
12314419d26bSmrg    int r;
12324419d26bSmrg    int rbase = 0;
1233e0a2b6dfSmrg
1234894e0ac8Smrg    TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
1235e0a2b6dfSmrg
1236e0a2b6dfSmrg    for (r = 0; r < graphic->actual_height; r++) {
12374419d26bSmrg	if (rbase >= r_min
12384419d26bSmrg	    && rbase <= r_max) {
12394419d26bSmrg	    int c;
12404419d26bSmrg	    int cbase = 0;
1241894e0ac8Smrg	    for (c = 0; c < graphic->actual_width; c++) {
12424419d26bSmrg		if (cbase >= c_min
12434419d26bSmrg		    && cbase <= c_max) {
12444419d26bSmrg		    const int cell = r * graphic->max_width + c;
12454419d26bSmrg		    ClrSpixel(graphic, cell);
1246894e0ac8Smrg		}
1247894e0ac8Smrg		cbase += pw;
1248894e0ac8Smrg	    }
1249e0a2b6dfSmrg	}
1250894e0ac8Smrg	rbase += ph;
1251e0a2b6dfSmrg    }
1252e0a2b6dfSmrg}
1253e0a2b6dfSmrg
1254e0a2b6dfSmrgstatic int
1255894e0ac8Smrgcompare_graphic_ids(const void *left, const void *right)
1256e0a2b6dfSmrg{
1257894e0ac8Smrg    const Graphic *l = *(const Graphic *const *) left;
1258894e0ac8Smrg    const Graphic *r = *(const Graphic *const *) right;
1259e0a2b6dfSmrg
1260e0a2b6dfSmrg    if (!l->valid || !r->valid)
1261e0a2b6dfSmrg	return 0;
126201037d57Smrg
126301037d57Smrg    if (l->bufferid < r->bufferid)
126401037d57Smrg	return -1;
126501037d57Smrg    else if (l->bufferid > r->bufferid)
126601037d57Smrg	return 1;
126701037d57Smrg
1268e0a2b6dfSmrg    if (l->id < r->id)
1269e0a2b6dfSmrg	return -1;
1270e0a2b6dfSmrg    else
1271e0a2b6dfSmrg	return 1;
1272e0a2b6dfSmrg}
1273e0a2b6dfSmrg
127401037d57Smrgstatic void
127501037d57Smrgclip_area(int *orig_x, int *orig_y, int *orig_w, int *orig_h,
127601037d57Smrg	  int clip_x, int clip_y, int clip_w, int clip_h)
1277e0a2b6dfSmrg{
127801037d57Smrg    if (*orig_x < clip_x) {
127901037d57Smrg	const int diff = clip_x - *orig_x;
128001037d57Smrg	*orig_x += diff;
128101037d57Smrg	*orig_w -= diff;
128201037d57Smrg    }
128301037d57Smrg    if (*orig_w > 0 && *orig_x + *orig_w > clip_x + clip_w) {
128401037d57Smrg	*orig_w -= (*orig_x + *orig_w) - (clip_x + clip_w);
128501037d57Smrg    }
128601037d57Smrg
128701037d57Smrg    if (*orig_y < clip_y) {
128801037d57Smrg	const int diff = clip_y - *orig_y;
128901037d57Smrg	*orig_y += diff;
129001037d57Smrg	*orig_h -= diff;
129101037d57Smrg    }
129201037d57Smrg    if (*orig_h > 0 && *orig_y + *orig_h > clip_y + clip_h) {
129301037d57Smrg	*orig_h -= (*orig_y + *orig_h) - (clip_y + clip_h);
129401037d57Smrg    }
129501037d57Smrg}
129601037d57Smrg
12974419d26bSmrgstatic Bool
12984419d26bSmrgGetGraphicsOrder(TScreen *screen,
12994419d26bSmrg		 int skip_clean,
13004419d26bSmrg		 Graphic *ordered_graphics[MAX_GRAPHICS],
13014419d26bSmrg		 unsigned *resultp)
130201037d57Smrg{
13034419d26bSmrg    unsigned ii;
130401037d57Smrg    unsigned active_count;
1305e0a2b6dfSmrg
13064419d26bSmrg    *resultp = active_count = 0;
1307894e0ac8Smrg    FOR_EACH_SLOT(ii) {
130801037d57Smrg	Graphic *graphic;
130901037d57Smrg	if (!(graphic = getActiveSlot(ii)))
131001037d57Smrg	    continue;
131101037d57Smrg	TRACE(("refreshing graphic %d on buffer %d, current buffer %d\n",
131201037d57Smrg	       graphic->id, graphic->bufferid, screen->whichBuf));
131301037d57Smrg	if (screen->whichBuf == 0) {
131401037d57Smrg	    if (graphic->bufferid != 0) {
131501037d57Smrg		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d\n",
131601037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
131701037d57Smrg		continue;
131801037d57Smrg	    }
131901037d57Smrg	} else {
132001037d57Smrg	    if (graphic->bufferid == 0 && graphic->charrow >= 0) {
132101037d57Smrg		TRACE(("skipping graphic %d from normal buffer (%d) when drawing screen=%d because it is not in scrollback area\n",
132201037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
132301037d57Smrg		continue;
132401037d57Smrg	    }
132501037d57Smrg	    if (graphic->bufferid == 1 &&
132601037d57Smrg		graphic->charrow + (graphic->actual_height +
132701037d57Smrg				    FontHeight(screen) - 1) /
132801037d57Smrg		FontHeight(screen) < 0) {
132901037d57Smrg		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d because it is completely in scrollback area\n",
133001037d57Smrg		       graphic->id, graphic->bufferid, screen->whichBuf));
133101037d57Smrg		continue;
133201037d57Smrg	    }
1333894e0ac8Smrg	}
13342e4f8982Smrg	if (graphic->hidden)
13352e4f8982Smrg	    continue;
133601037d57Smrg	ordered_graphics[active_count++] = graphic;
1337894e0ac8Smrg    }
133801037d57Smrg
133901037d57Smrg    if (active_count == 0)
13404419d26bSmrg	return False;
134101037d57Smrg    if (active_count > 1) {
1342894e0ac8Smrg	qsort(ordered_graphics,
134301037d57Smrg	      (size_t) active_count,
1344894e0ac8Smrg	      sizeof(ordered_graphics[0]),
1345894e0ac8Smrg	      compare_graphic_ids);
1346e0a2b6dfSmrg    }
1347e0a2b6dfSmrg
134801037d57Smrg    if (skip_clean) {
13494419d26bSmrg	unsigned jj;
135001037d57Smrg	unsigned skip_count;
1351e0a2b6dfSmrg
135201037d57Smrg	for (jj = 0; jj < active_count; ++jj) {
135301037d57Smrg	    if (ordered_graphics[jj]->dirty)
135401037d57Smrg		break;
1355e0a2b6dfSmrg	}
135601037d57Smrg	skip_count = jj;
135701037d57Smrg	if (skip_count == active_count)
13584419d26bSmrg	    return False;
1359e0a2b6dfSmrg
136001037d57Smrg	active_count -= skip_count;
136101037d57Smrg	for (jj = 0; jj < active_count; ++jj) {
136201037d57Smrg	    ordered_graphics[jj] = ordered_graphics[jj + skip_count];
136301037d57Smrg	}
1364e0a2b6dfSmrg    }
13654419d26bSmrg    *resultp = active_count;
13664419d26bSmrg    return True;
13674419d26bSmrg}
1368e0a2b6dfSmrg
13694419d26bSmrgstatic ColorRegister *
13704419d26bSmrgAllocGraphicsBuffer(TScreen *screen,
13714419d26bSmrg		    int ncols,
13724419d26bSmrg		    int nrows)
13734419d26bSmrg{
13744419d26bSmrg    int xx, yy;
13754419d26bSmrg    int const refresh_w = ncols * FontWidth(screen);
13764419d26bSmrg    int const refresh_h = nrows * FontHeight(screen);
13774419d26bSmrg    ColorRegister *buffer;
13784419d26bSmrg
13795307cd1aSmrg    if (!(buffer = TypeMallocN(ColorRegister,
13805307cd1aSmrg			         (unsigned) refresh_w * (unsigned) refresh_h))) {
138101037d57Smrg	TRACE(("unable to allocate %dx%d buffer for graphics refresh\n",
138201037d57Smrg	       refresh_w, refresh_h));
13834419d26bSmrg    } else {
13844419d26bSmrg	/* assuming two's complement, the memset will be much faster than loop */
13854419d26bSmrg	if ((unsigned short) null_color.r == 0xffff) {
13864419d26bSmrg	    memset(buffer, 0xff,
13874419d26bSmrg		   sizeof(ColorRegister) * (size_t) (refresh_h * refresh_w));
13884419d26bSmrg	} else {
13894419d26bSmrg	    for (yy = 0; yy < refresh_h; yy++) {
13904419d26bSmrg		for (xx = 0; xx < refresh_w; xx++) {
13914419d26bSmrg		    buffer[yy * refresh_w + xx] = null_color;
13924419d26bSmrg		}
13934419d26bSmrg	    }
139401037d57Smrg	}
139501037d57Smrg    }
13964419d26bSmrg    return buffer;
13974419d26bSmrg}
1398e0a2b6dfSmrg
13994419d26bSmrgtypedef struct {
14004419d26bSmrg    int x_min;
14014419d26bSmrg    int x_max;
14024419d26bSmrg    int y_min;
14034419d26bSmrg    int y_max;
14044419d26bSmrg} ClipLimits;
14054419d26bSmrg
14064419d26bSmrgstatic Boolean
14074419d26bSmrgRefreshClipped(TScreen *screen,
14084419d26bSmrg	       int leftcol,
14094419d26bSmrg	       int toprow,
14104419d26bSmrg	       int ncols,
14114419d26bSmrg	       int nrows,
14124419d26bSmrg	       Graphic *ordered_graphics[MAX_GRAPHICS],
14134419d26bSmrg	       unsigned active_count,
14144419d26bSmrg	       ColorRegister *buffer,
14154419d26bSmrg	       ClipLimits * result)
14164419d26bSmrg{
14174419d26bSmrg    int const scroll_y = screen->topline * FontHeight(screen);
14184419d26bSmrg    int const refresh_x = leftcol * FontWidth(screen);
14194419d26bSmrg    int const refresh_y = toprow * FontHeight(screen) + scroll_y;
14204419d26bSmrg    int const refresh_w = ncols * FontWidth(screen);
14214419d26bSmrg    int const refresh_h = nrows * FontHeight(screen);
14224419d26bSmrg    ClipLimits my_limits;
14234419d26bSmrg    unsigned jj;
14244419d26bSmrg
14254419d26bSmrg    int const altarea_x = 0;
14264419d26bSmrg    int const altarea_y = 0;
14274419d26bSmrg    int const altarea_w = Width(screen) * FontWidth(screen);
14284419d26bSmrg    int const altarea_h = Height(screen) * FontHeight(screen);
14294419d26bSmrg
14304419d26bSmrg    int const scrollarea_x = 0;
14314419d26bSmrg    int const scrollarea_y = scroll_y;
14324419d26bSmrg    int const scrollarea_w = Width(screen) * FontWidth(screen);
14334419d26bSmrg    int const scrollarea_h = -scroll_y;
14344419d26bSmrg
14354419d26bSmrg    int const mainarea_x = 0;
14364419d26bSmrg    int const mainarea_y = scroll_y;
14374419d26bSmrg    int const mainarea_w = Width(screen) * FontWidth(screen);
14384419d26bSmrg    int const mainarea_h = -scroll_y + Height(screen) * FontHeight(screen);
14394419d26bSmrg
14404419d26bSmrg    my_limits.x_min = refresh_x + refresh_w;
14414419d26bSmrg    my_limits.x_max = refresh_x - 1;
14424419d26bSmrg    my_limits.y_min = refresh_y + refresh_h;
14434419d26bSmrg    my_limits.y_max = refresh_y - 1;
14444419d26bSmrg    for (jj = 0; jj < active_count; ++jj) {
14454419d26bSmrg	Graphic *graphic = ordered_graphics[jj];
14464419d26bSmrg	int draw_x = graphic->charcol * FontWidth(screen);
14474419d26bSmrg	int draw_y = graphic->charrow * FontHeight(screen);
14484419d26bSmrg	int draw_w = graphic->actual_width;
14494419d26bSmrg	int draw_h = graphic->actual_height;
14504419d26bSmrg
14514419d26bSmrg	if (screen->whichBuf != 0) {
14524419d26bSmrg	    if (graphic->bufferid != 0) {
14534419d26bSmrg		/* clip to alt buffer */
14544419d26bSmrg		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
14554419d26bSmrg			  altarea_x, altarea_y, altarea_w, altarea_h);
145601037d57Smrg	    } else {
14574419d26bSmrg		/* clip to scrollback area */
145801037d57Smrg		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
14594419d26bSmrg			  scrollarea_x, scrollarea_y,
14604419d26bSmrg			  scrollarea_w, scrollarea_h);
146101037d57Smrg	    }
14624419d26bSmrg	} else {
14634419d26bSmrg	    /* clip to scrollback + normal area */
146401037d57Smrg	    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
14654419d26bSmrg		      mainarea_x, mainarea_y,
14664419d26bSmrg		      mainarea_w, mainarea_h);
14674419d26bSmrg	}
14684419d26bSmrg
14694419d26bSmrg	clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
14704419d26bSmrg		  refresh_x, refresh_y, refresh_w, refresh_h);
14714419d26bSmrg
14724419d26bSmrg	TRACE(("refresh: graph=%u\n", jj));
14734419d26bSmrg	TRACE(("         refresh_x=%d refresh_y=%d refresh_w=%d refresh_h=%d\n",
14744419d26bSmrg	       refresh_x, refresh_y, refresh_w, refresh_h));
14754419d26bSmrg	TRACE(("         draw_x=%d draw_y=%d draw_w=%d draw_h=%d\n",
14764419d26bSmrg	       draw_x, draw_y, draw_w, draw_h));
14774419d26bSmrg
14784419d26bSmrg	if (draw_w > 0 && draw_h > 0) {
14794419d26bSmrg	    refresh_graphic(screen, graphic, buffer,
14804419d26bSmrg			    refresh_x, refresh_y,
14814419d26bSmrg			    refresh_w, refresh_h,
14824419d26bSmrg			    draw_x, draw_y,
14834419d26bSmrg			    draw_w, draw_h);
14844419d26bSmrg	    if (draw_x < my_limits.x_min)
14854419d26bSmrg		my_limits.x_min = draw_x;
14864419d26bSmrg	    if (draw_x + draw_w - 1 > my_limits.x_max)
14874419d26bSmrg		my_limits.x_max = draw_x + draw_w - 1;
14884419d26bSmrg	    if (draw_y < my_limits.y_min)
14894419d26bSmrg		my_limits.y_min = draw_y;
14904419d26bSmrg	    if (draw_y + draw_h - 1 > my_limits.y_max)
14914419d26bSmrg		my_limits.y_max = draw_y + draw_h - 1;
1492e0a2b6dfSmrg	}
14934419d26bSmrg	graphic->dirty = False;
149401037d57Smrg    }
1495e0a2b6dfSmrg
14964419d26bSmrg    if (my_limits.x_max < refresh_x ||
14974419d26bSmrg	my_limits.x_min > refresh_x + refresh_w - 1 ||
14984419d26bSmrg	my_limits.y_max < refresh_y ||
14994419d26bSmrg	my_limits.y_min > refresh_y + refresh_h - 1) {
15004419d26bSmrg	return False;
1501e0a2b6dfSmrg    }
15024419d26bSmrg    *result = my_limits;
15034419d26bSmrg    return True;
15044419d26bSmrg}
150501037d57Smrg
15064419d26bSmrgstatic Boolean
15074419d26bSmrgFindGraphicHoles(int refresh_x,
15084419d26bSmrg		 int refresh_y,
15094419d26bSmrg		 int refresh_w,
15104419d26bSmrg		 ColorRegister *buffer,
15114419d26bSmrg		 ClipLimits * limits,
15124419d26bSmrg		 unsigned *result)
15134419d26bSmrg{
15144419d26bSmrg    const int y_min = limits->y_min - refresh_y;
15154419d26bSmrg    const int y_max = limits->y_max - refresh_y;
15164419d26bSmrg    const int x_min = limits->x_min - refresh_x;
15174419d26bSmrg    const int x_max = limits->x_max - refresh_x;
15184419d26bSmrg    const ColorRegister *base = buffer + (y_min * refresh_w);
15194419d26bSmrg    int xx, yy;
15204419d26bSmrg
15214419d26bSmrg    unsigned holes = 0U;
15224419d26bSmrg    unsigned non_holes = 0U;
15234419d26bSmrg
15244419d26bSmrg    for (yy = y_min; yy <= y_max; yy++) {
15254419d26bSmrg	const ColorRegister *scan = base + x_min;
15264419d26bSmrg	for (xx = x_min; xx <= x_max; xx++) {
15274419d26bSmrg	    if (scan->r < 0 || scan->g < 0 || scan->b < 0) {
15284419d26bSmrg		holes++;
15294419d26bSmrg	    } else {
15304419d26bSmrg		non_holes++;
153101037d57Smrg	    }
15324419d26bSmrg	    if (non_holes && holes)
15334419d26bSmrg		goto finish;
15344419d26bSmrg	    ++scan;
153501037d57Smrg	}
15364419d26bSmrg	base += refresh_w;
153701037d57Smrg    }
153801037d57Smrg
15394419d26bSmrg  finish:
15404419d26bSmrg    *result = holes;
15414419d26bSmrg    return (non_holes != 0);
15424419d26bSmrg}
15434419d26bSmrg
15444419d26bSmrg/* the coordinates are relative to the screen */
15454419d26bSmrgstatic void
15464419d26bSmrgrefresh_graphics(XtermWidget xw,
15474419d26bSmrg		 int leftcol,
15484419d26bSmrg		 int toprow,
15494419d26bSmrg		 int ncols,
15504419d26bSmrg		 int nrows,
15514419d26bSmrg		 int skip_clean)
15524419d26bSmrg{
15534419d26bSmrg    TScreen *const screen = TScreenOf(xw);
15544419d26bSmrg    Display *const display = screen->display;
15554419d26bSmrg    Window const drawable = VDrawable(screen);
15564419d26bSmrg    int const scroll_y = screen->topline * FontHeight(screen);
15574419d26bSmrg    int const refresh_x = leftcol * FontWidth(screen);
15584419d26bSmrg    int const refresh_y = toprow * FontHeight(screen) + scroll_y;
15594419d26bSmrg    int const refresh_w = ncols * FontWidth(screen);
15604419d26bSmrg
15614419d26bSmrg    Graphic *ordered_graphics[MAX_GRAPHICS];
15624419d26bSmrg    unsigned active_count;
15634419d26bSmrg    unsigned holes;
15644419d26bSmrg    int xx, yy;
15654419d26bSmrg
15664419d26bSmrg    ColorRegister *buffer;
15674419d26bSmrg    ClipLimits clip_limits;
15684419d26bSmrg
15694419d26bSmrg    if_TRACE(int const refresh_h = nrows * FontHeight(screen));
15704419d26bSmrg
15714419d26bSmrg    if (!GetGraphicsOrder(screen, skip_clean, ordered_graphics, &active_count))
15724419d26bSmrg	return;
15734419d26bSmrg
15744419d26bSmrg    if (!valid_graphics) {
15754419d26bSmrg	memset(&xgcv, 0, sizeof(xgcv));
15764419d26bSmrg	xgcv.graphics_exposures = False;
15774419d26bSmrg	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
15784419d26bSmrg	last_color = null_color;
15794419d26bSmrg	gc_color = null_color;
15805104ee6eSmrg	if (graphics_gc == NULL) {
15814419d26bSmrg	    TRACE(("unable to allocate GC for graphics refresh\n"));
15824419d26bSmrg	    valid_graphics = -1;
15834419d26bSmrg	} else {
15844419d26bSmrg	    valid_graphics = 1;
15854419d26bSmrg	}
15864419d26bSmrg    }
15874419d26bSmrg    if (valid_graphics < 0)
15884419d26bSmrg	return;
15894419d26bSmrg
15904419d26bSmrg    if ((buffer = AllocGraphicsBuffer(screen, ncols, nrows)) == NULL)
15914419d26bSmrg	return;
15924419d26bSmrg
15934419d26bSmrg    TRACE(("refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d (%d,%d %dx%d)\n",
15944419d26bSmrg	   screen->topline,
15954419d26bSmrg	   leftcol, toprow,
15964419d26bSmrg	   nrows, ncols,
15974419d26bSmrg	   refresh_x, refresh_y,
15984419d26bSmrg	   refresh_w, refresh_h));
15994419d26bSmrg
16004419d26bSmrg    if (!RefreshClipped(screen, leftcol, toprow, ncols, nrows,
16014419d26bSmrg			ordered_graphics,
16024419d26bSmrg			active_count,
16034419d26bSmrg			buffer,
16044419d26bSmrg			&clip_limits)) {
16054419d26bSmrg	free(buffer);
16064419d26bSmrg	return;
16074419d26bSmrg    }
16084419d26bSmrg
16094419d26bSmrg    if (!FindGraphicHoles(refresh_x,
16104419d26bSmrg			  refresh_y,
16114419d26bSmrg			  refresh_w,
16124419d26bSmrg			  buffer,
16134419d26bSmrg			  &clip_limits,
16144419d26bSmrg			  &holes)) {
161501037d57Smrg	TRACE(("refresh: visible graphics areas are erased; nothing to do\n"));
161601037d57Smrg	free(buffer);
161701037d57Smrg	return;
161801037d57Smrg    }
161901037d57Smrg
162001037d57Smrg    /*
162101037d57Smrg     * If we have any holes we can't just copy an image rectangle, and masking
162201037d57Smrg     * with bitmaps is very expensive.  This fallback is surprisingly faster
162301037d57Smrg     * than the XPutImage version in some cases, but I don't know why.
162401037d57Smrg     * (This is even though there's no X11 primitive for drawing a horizontal
162501037d57Smrg     * line of height one and no attempt is made to handle multiple lines at
162601037d57Smrg     * once.)
162701037d57Smrg     */
162801037d57Smrg    if (holes > 0U) {
162901037d57Smrg	int run;
163001037d57Smrg
163101037d57Smrg	run = 0;
16324419d26bSmrg	for (yy = clip_limits.y_min - refresh_y;
16334419d26bSmrg	     yy <= clip_limits.y_max - refresh_y;
16344419d26bSmrg	     yy++) {
16354419d26bSmrg	    for (xx = clip_limits.x_min - refresh_x;
16364419d26bSmrg		 xx <= clip_limits.x_max - refresh_x;
163701037d57Smrg		 xx++) {
163801037d57Smrg		const ColorRegister color = buffer[yy * refresh_w + xx];
163901037d57Smrg
164001037d57Smrg		if (color.r < 0 || color.g < 0 || color.b < 0) {
164101037d57Smrg		    last_color = color;
164201037d57Smrg		    if (run > 0) {
164301037d57Smrg			XDrawLine(display, drawable, graphics_gc,
164401037d57Smrg				  OriginX(screen) + refresh_x + xx - run,
164501037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy,
164601037d57Smrg				  OriginX(screen) + refresh_x + xx - 1,
164701037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy);
164801037d57Smrg			run = 0;
164901037d57Smrg		    }
165001037d57Smrg		    continue;
165101037d57Smrg		}
165201037d57Smrg
1653f2e35a3aSmrg		if (DiffColor(color, last_color)) {
165401037d57Smrg		    last_color = color;
165501037d57Smrg		    if (run > 0) {
165601037d57Smrg			XDrawLine(display, drawable, graphics_gc,
165701037d57Smrg				  OriginX(screen) + refresh_x + xx - run,
165801037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy,
165901037d57Smrg				  OriginX(screen) + refresh_x + xx - 1,
166001037d57Smrg				  (OriginY(screen) - scroll_y) + refresh_y + yy);
166101037d57Smrg			run = 0;
166201037d57Smrg		    }
166301037d57Smrg
1664f2e35a3aSmrg		    if (DiffColor(color, gc_color)) {
166501037d57Smrg			xgcv.foreground =
166601037d57Smrg			    color_register_to_xpixel(&color, xw);
166701037d57Smrg			XChangeGC(display, graphics_gc, GCForeground, &xgcv);
166801037d57Smrg			gc_color = color;
166901037d57Smrg		    }
167001037d57Smrg		}
167101037d57Smrg		run++;
167201037d57Smrg	    }
167301037d57Smrg	    if (run > 0) {
1674f2e35a3aSmrg		last_color = null_color;
167501037d57Smrg		XDrawLine(display, drawable, graphics_gc,
167601037d57Smrg			  OriginX(screen) + refresh_x + xx - run,
167701037d57Smrg			  (OriginY(screen) - scroll_y) + refresh_y + yy,
167801037d57Smrg			  OriginX(screen) + refresh_x + xx - 1,
167901037d57Smrg			  (OriginY(screen) - scroll_y) + refresh_y + yy);
168001037d57Smrg		run = 0;
168101037d57Smrg	    }
168201037d57Smrg	}
168301037d57Smrg    } else {
1684f2e35a3aSmrg	ColorRegister old_colors[2];
1685f2e35a3aSmrg	Pixel fg, old_result[2];
168601037d57Smrg	XImage *image;
168701037d57Smrg	char *imgdata;
16884419d26bSmrg	const unsigned image_w = ((unsigned) clip_limits.x_max + 1U -
16894419d26bSmrg				  (unsigned) clip_limits.x_min);
16904419d26bSmrg	const unsigned image_h = ((unsigned) clip_limits.y_max + 1U -
16914419d26bSmrg				  (unsigned) clip_limits.y_min);
1692f2e35a3aSmrg	int nn;
169301037d57Smrg
169401037d57Smrg	image = XCreateImage(display, xw->visInfo->visual,
169501037d57Smrg			     (unsigned) xw->visInfo->depth,
169601037d57Smrg			     ZPixmap, 0, NULL,
169701037d57Smrg			     image_w, image_h,
1698f2e35a3aSmrg			     (int) (sizeof(int) * 8U), 0);
169901037d57Smrg	if (!image) {
170001037d57Smrg	    TRACE(("unable to allocate XImage for graphics refresh\n"));
170101037d57Smrg	    free(buffer);
170201037d57Smrg	    return;
170301037d57Smrg	}
17045307cd1aSmrg	imgdata = TypeMallocN(char, (size_t)(image_h * (unsigned)image->bytes_per_line));
170501037d57Smrg	if (!imgdata) {
170601037d57Smrg	    TRACE(("unable to allocate XImage for graphics refresh\n"));
170701037d57Smrg	    XDestroyImage(image);
170801037d57Smrg	    free(buffer);
170901037d57Smrg	    return;
171001037d57Smrg	}
171101037d57Smrg	image->data = imgdata;
171201037d57Smrg
171301037d57Smrg	fg = 0U;
1714f2e35a3aSmrg	nn = 0;
1715f2e35a3aSmrg
1716f2e35a3aSmrg	/* two-level cache cuts down on lookup-calls */
1717f2e35a3aSmrg	old_result[0] = 0U;
1718f2e35a3aSmrg	old_result[1] = 0U;
1719f2e35a3aSmrg	old_colors[0] = null_color;
1720f2e35a3aSmrg	old_colors[1] = null_color;
1721f2e35a3aSmrg
17224419d26bSmrg	for (yy = clip_limits.y_min - refresh_y;
17234419d26bSmrg	     yy <= clip_limits.y_max - refresh_y;
17244419d26bSmrg	     yy++) {
17254419d26bSmrg	    for (xx = clip_limits.x_min - refresh_x;
17264419d26bSmrg		 xx <= clip_limits.x_max - refresh_x;
172701037d57Smrg		 xx++) {
172801037d57Smrg		const ColorRegister color = buffer[yy * refresh_w + xx];
172901037d57Smrg
1730f2e35a3aSmrg		if (DiffColor(color, old_colors[nn])) {
1731f2e35a3aSmrg		    if (DiffColor(color, old_colors[!nn])) {
1732f2e35a3aSmrg			nn = !nn;
1733f2e35a3aSmrg			fg = color_register_to_xpixel(&color, xw);
1734f2e35a3aSmrg			old_result[nn] = fg;
1735f2e35a3aSmrg			old_colors[nn] = color;
1736f2e35a3aSmrg		    } else {
1737f2e35a3aSmrg			nn = !nn;
1738f2e35a3aSmrg			fg = old_result[nn];
1739f2e35a3aSmrg		    }
174001037d57Smrg		}
174101037d57Smrg
1742f2e35a3aSmrg		XPutPixel(image,
17434419d26bSmrg			  xx + refresh_x - clip_limits.x_min,
17444419d26bSmrg			  yy + refresh_y - clip_limits.y_min, fg);
174501037d57Smrg	    }
174601037d57Smrg	}
174701037d57Smrg
174801037d57Smrg	XPutImage(display, drawable, graphics_gc, image,
174901037d57Smrg		  0, 0,
17504419d26bSmrg		  OriginX(screen) + clip_limits.x_min,
17514419d26bSmrg		  (OriginY(screen) - scroll_y) + clip_limits.y_min,
175201037d57Smrg		  image_w, image_h);
175301037d57Smrg	free(imgdata);
175401037d57Smrg	image->data = NULL;
175501037d57Smrg	XDestroyImage(image);
175601037d57Smrg    }
175701037d57Smrg
175801037d57Smrg    free(buffer);
175901037d57Smrg    XFlush(display);
176001037d57Smrg}
176101037d57Smrg
176201037d57Smrgvoid
176301037d57Smrgrefresh_displayed_graphics(XtermWidget xw,
176401037d57Smrg			   int leftcol,
176501037d57Smrg			   int toprow,
176601037d57Smrg			   int ncols,
176701037d57Smrg			   int nrows)
176801037d57Smrg{
176901037d57Smrg    refresh_graphics(xw, leftcol, toprow, ncols, nrows, 0);
177001037d57Smrg}
177101037d57Smrg
177201037d57Smrgvoid
177301037d57Smrgrefresh_modified_displayed_graphics(XtermWidget xw)
177401037d57Smrg{
177501037d57Smrg    TScreen const *screen = TScreenOf(xw);
177601037d57Smrg    refresh_graphics(xw, 0, 0, MaxCols(screen), MaxRows(screen), 1);
1777e0a2b6dfSmrg}
1778e0a2b6dfSmrg
1779894e0ac8Smrgvoid
178001037d57Smrgscroll_displayed_graphics(XtermWidget xw, int rows)
1781e0a2b6dfSmrg{
1782f2e35a3aSmrg    if (used_graphics) {
1783f2e35a3aSmrg	TScreen const *screen = TScreenOf(xw);
1784f2e35a3aSmrg	unsigned ii;
1785e0a2b6dfSmrg
1786f2e35a3aSmrg	TRACE(("graphics scroll: moving all up %d rows\n", rows));
1787f2e35a3aSmrg	/* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
1788894e0ac8Smrg
1789f2e35a3aSmrg	FOR_EACH_SLOT(ii) {
1790f2e35a3aSmrg	    Graphic *graphic;
17912e4f8982Smrg
1792f2e35a3aSmrg	    if (!(graphic = getActiveSlot(ii)))
1793f2e35a3aSmrg		continue;
1794f2e35a3aSmrg	    if (graphic->bufferid != screen->whichBuf)
1795f2e35a3aSmrg		continue;
1796f2e35a3aSmrg	    if (graphic->hidden)
1797f2e35a3aSmrg		continue;
1798e0a2b6dfSmrg
1799f2e35a3aSmrg	    graphic->charrow -= rows;
1800f2e35a3aSmrg	}
1801e0a2b6dfSmrg    }
1802e0a2b6dfSmrg}
1803e0a2b6dfSmrg
1804894e0ac8Smrgvoid
1805e0a2b6dfSmrgpixelarea_clear_displayed_graphics(TScreen const *screen,
1806e0a2b6dfSmrg				   int winx,
1807e0a2b6dfSmrg				   int winy,
1808e0a2b6dfSmrg				   int w,
1809e0a2b6dfSmrg				   int h)
1810e0a2b6dfSmrg{
1811894e0ac8Smrg    unsigned ii;
1812e0a2b6dfSmrg
1813f2e35a3aSmrg    if (!used_graphics)
1814f2e35a3aSmrg	return;
1815f2e35a3aSmrg
1816894e0ac8Smrg    FOR_EACH_SLOT(ii) {
181701037d57Smrg	Graphic *graphic;
181801037d57Smrg	/* FIXME: are these coordinates (scrolled) screen-relative? */
181901037d57Smrg	int const scroll_y = (screen->whichBuf == 0
182001037d57Smrg			      ? screen->topline * FontHeight(screen)
182101037d57Smrg			      : 0);
182201037d57Smrg	int graph_x;
182301037d57Smrg	int graph_y;
182401037d57Smrg	int x, y;
182501037d57Smrg
1826894e0ac8Smrg	if (!(graphic = getActiveSlot(ii)))
1827e0a2b6dfSmrg	    continue;
182801037d57Smrg	if (graphic->bufferid != screen->whichBuf)
182901037d57Smrg	    continue;
18302e4f8982Smrg	if (graphic->hidden)
18312e4f8982Smrg	    continue;
1832e0a2b6dfSmrg
183301037d57Smrg	graph_x = graphic->charcol * FontWidth(screen);
183401037d57Smrg	graph_y = graphic->charrow * FontHeight(screen);
183501037d57Smrg	x = winx - graph_x;
183601037d57Smrg	y = (winy - scroll_y) - graph_y;
1837e0a2b6dfSmrg
183801037d57Smrg	TRACE(("pixelarea clear graphics: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
1839e0a2b6dfSmrg	       screen->topline,
1840e0a2b6dfSmrg	       winx, winy,
1841e0a2b6dfSmrg	       w, h,
1842e0a2b6dfSmrg	       x, y));
1843894e0ac8Smrg	erase_graphic(graphic, x, y, w, h);
1844e0a2b6dfSmrg    }
1845e0a2b6dfSmrg}
1846e0a2b6dfSmrg
1847894e0ac8Smrgvoid
1848e0a2b6dfSmrgchararea_clear_displayed_graphics(TScreen const *screen,
1849e0a2b6dfSmrg				  int leftcol,
1850e0a2b6dfSmrg				  int toprow,
1851e0a2b6dfSmrg				  int ncols,
1852e0a2b6dfSmrg				  int nrows)
1853e0a2b6dfSmrg{
1854f2e35a3aSmrg    if (used_graphics) {
1855f2e35a3aSmrg	int const x = leftcol * FontWidth(screen);
1856f2e35a3aSmrg	int const y = toprow * FontHeight(screen);
1857f2e35a3aSmrg	int const w = ncols * FontWidth(screen);
1858f2e35a3aSmrg	int const h = nrows * FontHeight(screen);
1859e0a2b6dfSmrg
1860f2e35a3aSmrg	TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
1861f2e35a3aSmrg	       screen->topline,
1862f2e35a3aSmrg	       leftcol, toprow,
1863f2e35a3aSmrg	       nrows, ncols,
1864f2e35a3aSmrg	       x, y, w, h));
1865f2e35a3aSmrg	pixelarea_clear_displayed_graphics(screen, x, y, w, h);
1866f2e35a3aSmrg    }
1867e0a2b6dfSmrg}
1868e0a2b6dfSmrg
1869894e0ac8Smrgvoid
1870e0a2b6dfSmrgreset_displayed_graphics(TScreen const *screen)
1871e0a2b6dfSmrg{
187204b94745Smrg    init_color_registers(screen, getSharedRegisters());
1873894e0ac8Smrg
1874f2e35a3aSmrg    if (used_graphics) {
1875f2e35a3aSmrg	unsigned ii;
1876894e0ac8Smrg
1877f2e35a3aSmrg	TRACE(("resetting all graphics\n"));
1878f2e35a3aSmrg	FOR_EACH_SLOT(ii) {
1879f2e35a3aSmrg	    deactivateSlot(ii);
1880f2e35a3aSmrg	}
188104b94745Smrg#if OPT_REGIS_GRAPHICS
188204b94745Smrg	reset_regis();
188304b94745Smrg#endif
1884894e0ac8Smrg    }
1885894e0ac8Smrg}
1886894e0ac8Smrg
1887894e0ac8Smrg#ifdef NO_LEAKS
1888894e0ac8Smrgvoid
18894419d26bSmrgnoleaks_graphics(Display *dpy)
1890894e0ac8Smrg{
1891894e0ac8Smrg    unsigned ii;
1892894e0ac8Smrg
1893894e0ac8Smrg    FOR_EACH_SLOT(ii) {
1894894e0ac8Smrg	deactivateSlot(ii);
1895e0a2b6dfSmrg    }
18964419d26bSmrg    if (valid_graphics > 0)
18974419d26bSmrg	XFreeGC(dpy, graphics_gc);
1898e0a2b6dfSmrg}
1899894e0ac8Smrg#endif
1900