graphics.c revision 01037d57
11.20Slha/* $XTermId: graphics.c,v 1.62 2014/12/23 00:08:58 Ross.Combs Exp $ */
21.9Smikel
31.1Scgd/*
41.18Slukem * Copyright 2013,2014 by Ross Combs
51.11Scgd *
61.1Scgd *                         All Rights Reserved
71.20Slha *
81.14Sjdolecek * Permission is hereby granted, free of charge, to any person obtaining a
91.20Slha * copy of this software and associated documentation files (the
101.1Scgd * "Software"), to deal in the Software without restriction, including
111.15Sis * without limitation the rights to use, copy, modify, merge, publish,
121.18Slukem * distribute, sublicense, and/or sell copies of the Software, and to
131.18Slukem * permit persons to whom the Software is furnished to do so, subject to
141.18Slukem * the following conditions:
151.18Slukem *
161.18Slukem * The above copyright notice and this permission notice shall be included
171.13Smrg * in all copies or substantial portions of the Software.
181.1Scgd *
191.1Scgd * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 */
32
33#include <xterm.h>
34
35#include <stdio.h>
36#include <ctype.h>
37#include <stdlib.h>
38
39#include <data.h>
40#include <ptyx.h>
41
42#include <assert.h>
43#include <graphics.h>
44
45#undef DUMP_BITMAP
46#undef DUMP_COLORS
47#undef DEBUG_PALETTE
48#undef DEBUG_PIXEL
49#undef DEBUG_REFRESH
50
51/*
52 * graphics TODO list
53 *
54 * ReGIS:
55 * - find a suitable default alphabet zero font instead of scaling Xft fonts
56 * - input and output cursors
57 * - mouse input
58 * - investigate second graphic page for ReGIS -- does it also apply to text and sixel graphics? are the contents preserved?
59 * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
60 * - non-ASCII alphabets
61 * - enter/leave during a command
62 * - command display mode
63 * - re-rasterization on resize
64 * - macros
65 * - improved fills for narrow angles (track actual lines not just pixels)
66 *
67 * sixel:
68 * - fix problem where new_row < 0 during sixel parsing (see FIXME)
69 * - screen-capture support (need dialog of some sort for safety)
70 *
71 * VT55/VT105 waveform graphics
72 * - everything
73 *
74 * Tektronix:
75 * - color (VT340 4014 emulation, 41xx, IRAF GTERM, and also MS-DOS Kermit color support)
76 * - polygon fill (41xx)
77 * - clear area extension
78 * - area fill extension
79 * - pixel operations (RU/RS/RP)
80 * - research other 41xx and 42xx extensions
81 *
82 * common graphics features:
83 * - handle light/dark screen modes (CSI?5[hl])
84 * - update text fg/bg color which overlaps images
85 * - handle graphic updates in scroll regions (verify effect on graphics)
86 * - handle rectangular area copies (verify they work with graphics)
87 * - invalidate graphics under graphic if same origin, at least as big, and bg not transparent
88 * - invalidate graphic if completely scrolled past end of scrollback
89 * - invalidate graphic if all pixels are transparent/erased
90 * - invalidate graphic if completely scrolled out of alt buffer
91 * - posturize requested colors to match hardware palettes (e.g. only four possible shades on VT240)
92 * - color register report/restore
93 * - ability to select/copy graphics for pasting in other programs
94 * - ability to show non-scroll-mode sixel graphics in a separate window
95 * - ability to show ReGIS graphics in a separate window
96 * - ability to show Tektronix graphics in VT100 window
97 * - truncate graphics at bottom edge of window?
98 *
99 * new escape sequences:
100 * - way to query text font size without "window ops" (or make "window ops" permissions more fine grained)
101 * - way to query and set the number of graphics pages
102 *
103 * ReGIS extensions:
104 * - non-integer text scaling
105 * - free distortionless text rotation
106 * - font characteristics: bold/underline/italic
107 * - remove/increase arbitrary limits (pattern size, pages, alphabets, stack size, font names, etc.)
108 * - comment command
109 * - shade/fill with borders
110 * - sprites (copy portion of page into/out of buffer with scaling and rotation)
111 * - ellipses
112 * - 2D patterns
113 * - option to set actual graphic size (not just coordinate range)
114 * - gradients (for lines and fills)
115 * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
116 * - transparency
117 * - background color as stackable write control
118 * - true color (virtual color registers created upon lookup)
119 * - anti-aliasing
120 * - variable-width text
121 */
122
123/* font sizes:
124 * VT510:
125 *   80 Columns 132 Columns Maximum Number of Lines
126 *   10 x 16   6 x 16  26 lines + keyboard indicator line
127 *   10 x 13   6 x 13  26 lines + keyboard indicator line
128 *   10 x 10   6 x 10  42 lines + keyboard indicator line
129 *   10 x 8    6 x 8   53 lines + keyboard indicator line
130 */
131
132typedef struct allocated_color_register {
133    struct allocated_color_register *next;
134    Pixel pix;
135    short r, g, b;
136} AllocatedColorRegister;
137
138#define LOOKUP_WIDTH 16
139static AllocatedColorRegister *allocated_colors[LOOKUP_WIDTH][LOOKUP_WIDTH][LOOKUP_WIDTH];
140
141#define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
142
143static ColorRegister *shared_color_registers;
144static Graphic *displayed_graphics[MAX_GRAPHICS];
145static unsigned next_graphic_id = 0U;
146
147static ColorRegister *
148allocRegisters(void)
149{
150    return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
151}
152
153static Graphic *
154freeGraphic(Graphic *obj)
155{
156    if (obj) {
157	if (obj->pixels)
158	    free(obj->pixels);
159	if (obj->private_color_registers)
160	    free(obj->private_color_registers);
161	free(obj);
162    }
163    return NULL;
164}
165
166static Graphic *
167allocGraphic(int max_w, int max_h)
168{
169    Graphic *result = TypeCalloc(Graphic);
170    if (result) {
171	result->max_width = max_w;
172	result->max_height = max_h;
173	if (!(result->pixels = TypeCallocN(RegisterNum,
174					     (size_t) max_w * (size_t) max_h))) {
175	    result = freeGraphic(result);
176	} else if (!(result->private_color_registers = allocRegisters())) {
177	    result = freeGraphic(result);
178	}
179    }
180    return result;
181}
182
183static Graphic *
184getActiveSlot(unsigned n)
185{
186    if (n < MAX_GRAPHICS &&
187	displayed_graphics[n] &&
188	displayed_graphics[n]->valid) {
189	return displayed_graphics[n];
190    }
191    return NULL;
192}
193
194static Graphic *
195getInactiveSlot(const TScreen *screen, unsigned n)
196{
197    if (n < MAX_GRAPHICS &&
198	(!displayed_graphics[n] ||
199	 !displayed_graphics[n]->valid)) {
200	if (!displayed_graphics[n]) {
201	    displayed_graphics[n] = allocGraphic(screen->graphics_max_wide,
202						 screen->graphics_max_high);
203	}
204	return displayed_graphics[n];
205    }
206    return NULL;
207}
208
209static ColorRegister *
210getSharedRegisters(void)
211{
212    if (!shared_color_registers)
213	shared_color_registers = allocRegisters();
214    return shared_color_registers;
215}
216
217static void
218deactivateSlot(unsigned n)
219{
220    if (n < MAX_GRAPHICS) {
221	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
222    }
223}
224
225extern RegisterNum
226read_pixel(Graphic *graphic, int x, int y)
227{
228    if (x < 0 || x >= graphic->actual_width ||
229	y < 0 || y >= graphic->actual_height) {
230	return COLOR_HOLE;
231    }
232
233    return graphic->pixels[y * graphic->max_width + x];
234}
235
236#define _draw_pixel(G, X, Y, C) \
237    do { \
238        (G)->pixels[(Y) * (G)->max_width + (X)] = (RegisterNum) (C); \
239    } while (0)
240
241void
242draw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
243{
244    assert(color <= MAX_COLOR_REGISTERS);
245
246#ifdef DEBUG_PIXEL
247    TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
248	   x,
249	   y,
250	   color,
251	   COLOR_HOLE,
252	   ((color != COLOR_HOLE)
253	    ? (unsigned) graphic->color_registers[color].r : 0U),
254	   ((color != COLOR_HOLE)
255	    ? (unsigned) graphic->color_registers[color].g : 0U),
256	   ((color != COLOR_HOLE)
257	    ? (unsigned) graphic->color_registers[color].b : 0U)));
258#endif
259    if (x >= 0 && x < graphic->actual_width &&
260	y >= 0 && y < graphic->actual_height) {
261	_draw_pixel(graphic, x, y, color);
262	if (color < MAX_COLOR_REGISTERS)
263	    graphic->color_registers_used[color] = 1;
264    }
265}
266
267void
268draw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
269{
270    int x, y;
271    int tmp;
272
273    assert(color <= MAX_COLOR_REGISTERS);
274
275    if (x1 > x2) {
276	EXCHANGE(x1, x2, tmp);
277    }
278    if (y1 > y2) {
279	EXCHANGE(y1, y2, tmp);
280    }
281
282    if (x2 < 0 || x1 >= graphic->actual_width ||
283	y2 < 0 || y1 >= graphic->actual_height)
284	return;
285
286    if (x1 < 0)
287	x1 = 0;
288    if (x2 >= graphic->actual_width)
289	x2 = graphic->actual_width - 1;
290    if (y1 < 0)
291	y1 = 0;
292    if (y2 >= graphic->actual_height)
293	y2 = graphic->actual_height - 1;
294
295    if (color < MAX_COLOR_REGISTERS)
296	graphic->color_registers_used[color] = 1;
297    for (y = y1; y <= y2; y++)
298	for (x = x1; x <= x2; x++)
299	    _draw_pixel(graphic, x, y, color);
300}
301
302void
303draw_solid_line(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
304{
305    int x, y;
306    int dx, dy;
307    int dir, diff;
308
309    assert(color <= MAX_COLOR_REGISTERS);
310
311    dx = abs(x1 - x2);
312    dy = abs(y1 - y2);
313
314    if (dx > dy) {
315	if (x1 > x2) {
316	    int tmp;
317	    EXCHANGE(x1, x2, tmp);
318	    EXCHANGE(y1, y2, tmp);
319	}
320	if (y1 < y2)
321	    dir = 1;
322	else if (y1 > y2)
323	    dir = -1;
324	else
325	    dir = 0;
326
327	diff = 0;
328	y = y1;
329	for (x = x1; x <= x2; x++) {
330	    if (diff >= dx) {
331		diff -= dx;
332		y += dir;
333	    }
334	    diff += dy;
335	    draw_solid_pixel(graphic, x, y, color);
336	}
337    } else {
338	if (y1 > y2) {
339	    int tmp;
340	    EXCHANGE(x1, x2, tmp);
341	    EXCHANGE(y1, y2, tmp);
342	}
343	if (x1 < x2)
344	    dir = 1;
345	else if (x1 > x2)
346	    dir = -1;
347	else
348	    dir = 0;
349
350	diff = 0;
351	x = x1;
352	for (y = y1; y <= y2; y++) {
353	    if (diff >= dy) {
354		diff -= dy;
355		x += dir;
356	    }
357	    diff += dx;
358	    draw_solid_pixel(graphic, x, y, color);
359	}
360    }
361}
362
363void
364copy_overlapping_area(Graphic *graphic, int src_ul_x, int src_ul_y,
365		      int dst_ul_x, int dst_ul_y, unsigned w, unsigned h,
366		      unsigned default_color)
367{
368    int sx, ex, dx;
369    int sy, ey, dy;
370    int xx, yy;
371    int dst_x, dst_y;
372    int src_x, src_y;
373    RegisterNum color;
374
375    if (dst_ul_x <= src_ul_x) {
376	sx = 0;
377	ex = (int) w - 1;
378	dx = +1;
379    } else {
380	sx = (int) w - 1;
381	ex = 0;
382	dx = -1;
383    }
384
385    if (dst_ul_y <= src_ul_y) {
386	sy = 0;
387	ey = (int) h - 1;
388	dy = +1;
389    } else {
390	sy = (int) h - 1;
391	ey = 0;
392	dy = -1;
393    }
394
395    for (yy = sy; yy != ey + dy; yy += dy) {
396	dst_y = dst_ul_y + yy;
397	src_y = src_ul_y + yy;
398	if (dst_y < 0 || dst_y >= (int) graphic->actual_height)
399	    continue;
400
401	for (xx = sx; xx != ex + dx; xx += dx) {
402	    dst_x = dst_ul_x + xx;
403	    src_x = src_ul_x + xx;
404	    if (dst_x < 0 || dst_x >= (int) graphic->actual_width)
405		continue;
406
407	    if (src_x < 0 || src_x >= (int) graphic->actual_width ||
408		src_y < 0 || src_y >= (int) graphic->actual_height)
409		color = (RegisterNum) default_color;
410	    else
411		color = graphic->pixels[(unsigned) (src_y *
412						    graphic->max_width) +
413					(unsigned) src_x];
414
415	    graphic->pixels[(unsigned) (dst_y * graphic->max_width) +
416			    (unsigned) dst_x] = color;
417	}
418    }
419}
420
421static void
422set_color_register(ColorRegister *color_registers,
423		   unsigned color,
424		   int r,
425		   int g,
426		   int b)
427{
428    ColorRegister *reg = &color_registers[color];
429    reg->r = (short) r;
430    reg->g = (short) g;
431    reg->b = (short) b;
432}
433
434/* Graphics which don't use private colors will act as if they are using a
435 * device-wide color palette.
436 */
437static void
438set_shared_color_register(unsigned color, int r, int g, int b)
439{
440    Graphic *graphic;
441    unsigned ii;
442
443    assert(color < MAX_COLOR_REGISTERS);
444
445    set_color_register(getSharedRegisters(), color, r, g, b);
446
447    FOR_EACH_SLOT(ii) {
448	if (!(graphic = getActiveSlot(ii)))
449	    continue;
450	if (graphic->private_colors)
451	    continue;
452
453	if (graphic->color_registers_used[ii]) {
454	    graphic->dirty = 1;
455	}
456    }
457}
458
459void
460update_color_register(Graphic *graphic,
461		      unsigned color,
462		      int r,
463		      int g,
464		      int b)
465{
466    assert(color < MAX_COLOR_REGISTERS);
467
468    if (graphic->private_colors) {
469	set_color_register(graphic->private_color_registers,
470			   color, r, g, b);
471	if (graphic->color_registers_used[color]) {
472	    graphic->dirty = 1;
473	}
474	graphic->color_registers_used[color] = 1;
475    } else {
476	set_shared_color_register(color, r, g, b);
477    }
478}
479
480#define SQUARE(X) ( (X) * (X) )
481
482RegisterNum
483find_color_register(ColorRegister const *color_registers, int r, int g, int b)
484{
485    unsigned i;
486    unsigned d;
487    unsigned closest_index;
488    unsigned closest_distance;
489
490    /* I have no idea what algorithm DEC used for this.
491     * The documentation warns that it is unpredictable, especially with values
492     * far away from any allocated color so it is probably a very simple
493     * heuristic rather than something fancy like finding the minimum distance
494     * in a linear perceptive color space.
495     */
496    closest_index = MAX_COLOR_REGISTERS;
497    closest_distance = 0U;
498    for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
499	d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
500			SQUARE(3 * (color_registers[i].g - g)) +
501			SQUARE(1 * (color_registers[i].b - b)));
502	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
503	    closest_index = i;
504	    closest_distance = d;
505	}
506    }
507
508    TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
509	   r, g, b,
510	   closest_index,
511	   closest_distance,
512	   color_registers[closest_index].r,
513	   color_registers[closest_index].g,
514	   color_registers[closest_index].b));
515    return (RegisterNum) closest_index;
516}
517
518static void
519init_color_registers(ColorRegister *color_registers, int terminal_id)
520{
521    TRACE(("setting initial colors for terminal %d\n", terminal_id));
522    {
523	unsigned i;
524
525	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
526	    set_color_register(color_registers, (RegisterNum) i, 0, 0, 0);
527	}
528    }
529
530    /*
531     * default color registers:
532     *     (mono) (color)
533     * VK100/GIGI (fixed)
534     * VT125:
535     *   0: 0%      0%
536     *   1: 33%     blue
537     *   2: 66%     red
538     *   3: 100%    green
539     * VT240:
540     *   0: 0%      0%
541     *   1: 33%     blue
542     *   2: 66%     red
543     *   3: 100%    green
544     * VT241:
545     *   0: 0%      0%
546     *   1: 33%     blue
547     *   2: 66%     red
548     *   3: 100%    green
549     * VT330:
550     *   0: 0%      0%              (bg for light on dark mode)
551     *   1: 33%     blue (red?)
552     *   2: 66%     red (green?)
553     *   3: 100%    green (yellow?) (fg for light on dark mode)
554     * VT340:
555     *   0: 0%      0%              (bg for light on dark mode)
556     *   1: 14%     blue
557     *   2: 29%     red
558     *   3: 43%     green
559     *   4: 57%     magenta
560     *   5: 71%     cyan
561     *   6: 86%     yellow
562     *   7: 100%    50%             (fg for light on dark mode)
563     *   8: 0%      25%
564     *   9: 14%     gray-blue
565     *  10: 29%     gray-red
566     *  11: 43%     gray-green
567     *  12: 57%     gray-magenta
568     *  13: 71%     gray-cyan
569     *  14: 86%     gray-yellow
570     *  15: 100%    75%             ("white")
571     * VT382:
572     *   ? (FIXME: B&W only?)
573     * dxterm:
574     *  ?
575     */
576    switch (terminal_id) {
577    case 125:
578    case 241:
579	set_color_register(color_registers, 0, 0, 0, 0);
580	set_color_register(color_registers, 1, 0, 0, 100);
581	set_color_register(color_registers, 2, 0, 100, 0);
582	set_color_register(color_registers, 3, 100, 0, 0);
583	break;
584    case 240:
585    case 330:
586	set_color_register(color_registers, 0, 0, 0, 0);
587	set_color_register(color_registers, 1, 33, 33, 33);
588	set_color_register(color_registers, 2, 66, 66, 66);
589	set_color_register(color_registers, 3, 100, 100, 100);
590	break;
591    case 340:
592    default:
593	set_color_register(color_registers, 0, 0, 0, 0);
594	set_color_register(color_registers, 1, 20, 20, 80);
595	set_color_register(color_registers, 2, 80, 13, 13);
596	set_color_register(color_registers, 3, 20, 80, 20);
597	set_color_register(color_registers, 4, 80, 20, 80);
598	set_color_register(color_registers, 5, 20, 80, 80);
599	set_color_register(color_registers, 6, 80, 80, 20);
600	set_color_register(color_registers, 7, 53, 53, 53);
601	set_color_register(color_registers, 8, 26, 26, 26);
602	set_color_register(color_registers, 9, 33, 33, 60);
603	set_color_register(color_registers, 10, 60, 26, 26);
604	set_color_register(color_registers, 11, 33, 60, 33);
605	set_color_register(color_registers, 12, 60, 33, 60);
606	set_color_register(color_registers, 13, 33, 60, 60);
607	set_color_register(color_registers, 14, 60, 60, 33);
608	set_color_register(color_registers, 15, 80, 80, 80);
609	break;
610    case 382:			/* FIXME: verify */
611	set_color_register(color_registers, 0, 0, 0, 0);
612	set_color_register(color_registers, 1, 100, 100, 100);
613	break;
614    }
615
616#ifdef DEBUG_PALETTE
617    {
618	unsigned i;
619
620	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
621	    TRACE(("initial value for register %03u: %d,%d,%d\n",
622		   i,
623		   color_registers[i].r,
624		   color_registers[i].g,
625		   color_registers[i].b));
626	}
627    }
628#endif
629}
630
631unsigned
632get_color_register_count(TScreen const *screen)
633{
634    unsigned num_color_registers;
635
636    if (screen->numcolorregisters >= 0) {
637	num_color_registers = (unsigned) screen->numcolorregisters;
638    } else {
639	num_color_registers = 0U;
640    }
641
642    if (num_color_registers > 1U) {
643	if (num_color_registers > MAX_COLOR_REGISTERS)
644	    return MAX_COLOR_REGISTERS;
645	return num_color_registers;
646    }
647
648    /*
649     * color capabilities:
650     * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
651     * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
652     * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
653     * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
654     * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
655     * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
656     * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
657     * dxterm      ?
658     */
659    switch (screen->terminal_id) {
660    case 125:
661	return 4U;
662    case 240:
663	return 4U;
664    case 241:
665	return 4U;
666    case 330:
667	return 4U;
668    case 340:
669	return 16U;
670    case 382:
671	return 2U;
672    default:
673	/* unknown graphics model -- might as well be generous */
674	return MAX_COLOR_REGISTERS;
675    }
676}
677
678static void
679init_graphic(Graphic *graphic,
680	     unsigned type,
681	     int terminal_id,
682	     int charrow,
683	     int charcol,
684	     unsigned num_color_registers,
685	     int private_colors)
686{
687    const unsigned max_pixels = (unsigned) (graphic->max_width *
688					    graphic->max_height);
689    unsigned i;
690
691    TRACE(("initializing graphic object\n"));
692
693    graphic->dirty = 1;
694    for (i = 0U; i < max_pixels; i++)
695	graphic->pixels[i] = COLOR_HOLE;
696    memset(graphic->color_registers_used, 0, sizeof(graphic->color_registers_used));
697
698    /*
699     * text and graphics interactions:
700     * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
701     * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
702     * VT382                     text writes on top of graphics buffer FIXME: verify
703     * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
704     */
705
706    /*
707     * dimensions (ReGIS logical, physical):
708     * VK100/GIGI  768x4??  768x240(status?)
709     * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
710     * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
711     * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
712     * VT330       800x480  800x480(+?status)
713     * VT340       800x480  800x480(+?status)
714     * VT382       960x750  sixel only
715     * dxterm      ?x? ?x?  variable?
716     */
717
718    graphic->actual_width = 0;
719    graphic->actual_height = 0;
720
721    graphic->pixw = 1;
722    graphic->pixh = 1;
723
724    graphic->valid_registers = num_color_registers;
725    TRACE(("%d color registers\n", graphic->valid_registers));
726
727    graphic->private_colors = private_colors;
728    if (graphic->private_colors) {
729	TRACE(("using private color registers\n"));
730	init_color_registers(graphic->private_color_registers, terminal_id);
731	graphic->color_registers = graphic->private_color_registers;
732    } else {
733	TRACE(("using shared color registers\n"));
734	graphic->color_registers = getSharedRegisters();
735    }
736
737    graphic->charrow = charrow;
738    graphic->charcol = charcol;
739    graphic->type = type;
740    graphic->valid = 0;
741}
742
743Graphic *
744get_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
745{
746    TScreen const *screen = TScreenOf(xw);
747    int bufferid = screen->whichBuf;
748    int terminal_id = screen->terminal_id;
749    Graphic *graphic;
750    unsigned ii;
751
752    FOR_EACH_SLOT(ii) {
753	if ((graphic = getInactiveSlot(screen, ii))) {
754	    TRACE(("using fresh graphic index=%u id=%u\n", ii, next_graphic_id));
755	    break;
756	}
757    }
758
759    /* if none are free, recycle the graphic scrolled back the farthest */
760    if (!graphic) {
761	int min_charrow = 0;
762	Graphic *min_graphic = NULL;
763
764	FOR_EACH_SLOT(ii) {
765	    if (!(graphic = getActiveSlot(ii)))
766		continue;
767	    if (!min_graphic || graphic->charrow < min_charrow) {
768		min_charrow = graphic->charrow;
769		min_graphic = graphic;
770	    }
771	}
772	TRACE(("recycling old graphic index=%u id=%u\n", ii, next_graphic_id));
773	graphic = min_graphic;
774    }
775
776    if (graphic) {
777	unsigned num_color_registers;
778	num_color_registers = get_color_register_count(screen);
779	graphic->xw = xw;
780	graphic->bufferid = bufferid;
781	graphic->id = next_graphic_id++;
782	init_graphic(graphic,
783		     type,
784		     terminal_id,
785		     charrow,
786		     charcol,
787		     num_color_registers,
788		     screen->privatecolorregisters);
789    }
790    return graphic;
791}
792
793Graphic *
794get_new_or_matching_graphic(XtermWidget xw,
795			    int charrow,
796			    int charcol,
797			    int actual_width,
798			    int actual_height,
799			    unsigned type)
800{
801    TScreen const *screen = TScreenOf(xw);
802    int bufferid = screen->whichBuf;
803    Graphic *graphic;
804    unsigned ii;
805
806    FOR_EACH_SLOT(ii) {
807	if ((graphic = getActiveSlot(ii)) &&
808	    graphic->type == type &&
809	    graphic->bufferid == bufferid &&
810	    graphic->charrow == charrow &&
811	    graphic->charcol == charcol &&
812	    graphic->actual_width == actual_width &&
813	    graphic->actual_height == actual_height) {
814	    TRACE(("found existing graphic index=%u id=%u\n", ii, graphic->id));
815	    return graphic;
816	}
817    }
818
819    /* if no match get a new graphic */
820    if ((graphic = get_new_graphic(xw, charrow, charcol, type))) {
821	graphic->actual_width = actual_width;
822	graphic->actual_height = actual_height;
823    }
824    return graphic;
825}
826
827static int
828lookup_allocated_color(const ColorRegister *reg, Pixel *pix)
829{
830    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
831    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
832    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
833    const AllocatedColorRegister *search;
834
835    for (search = allocated_colors[rr][gg][bb]; search; search = search->next) {
836	if (search->r == reg->r &&
837	    search->g == reg->g &&
838	    search->b == reg->b) {
839	    *pix = search->pix;
840	    return 1;
841	}
842    }
843
844    *pix = 0UL;
845    return 0;
846}
847
848#define ScaleForXColor(s) (unsigned short) ((long)(s) * 65535 / CHANNEL_MAX)
849
850static int
851save_allocated_color(const ColorRegister *reg, XtermWidget xw, Pixel *pix)
852{
853    unsigned const rr = ((unsigned) reg->r * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
854    unsigned const gg = ((unsigned) reg->g * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
855    unsigned const bb = ((unsigned) reg->b * (LOOKUP_WIDTH - 1)) / CHANNEL_MAX;
856    XColor xcolor;
857    AllocatedColorRegister *new_color;
858
859    xcolor.pixel = 0UL;
860    xcolor.red = ScaleForXColor(reg->r);
861    xcolor.green = ScaleForXColor(reg->g);
862    xcolor.blue = ScaleForXColor(reg->b);
863    xcolor.flags = DoRed | DoGreen | DoBlue;
864    if (!allocateBestRGB(xw, &xcolor)) {
865	TRACE(("unable to allocate xcolor\n"));
866	*pix = 0UL;
867	return 0;
868    }
869
870    *pix = xcolor.pixel;
871
872    if (!(new_color = malloc(sizeof(*new_color)))) {
873	TRACE(("unable to save pixel %lu\n", (unsigned long) *pix));
874	return 0;
875    }
876    new_color->r = reg->r;
877    new_color->g = reg->g;
878    new_color->b = reg->b;
879    new_color->pix = *pix;
880    new_color->next = allocated_colors[rr][gg][bb];
881
882    allocated_colors[rr][gg][bb] = new_color;
883
884    return 1;
885}
886
887static Pixel
888color_register_to_xpixel(const ColorRegister *reg, XtermWidget xw)
889{
890    Pixel pix;
891
892    if (!lookup_allocated_color(reg, &pix))
893	save_allocated_color(reg, xw, &pix);
894
895    /* FIXME: with so many possible colors we need to determine
896     * when to free them to be nice to PseudoColor displays
897     */
898    return pix;
899}
900
901static void
902refresh_graphic(TScreen const *screen,
903		Graphic const *graphic,
904		ColorRegister *buffer,
905		int refresh_x,
906		int refresh_y,
907		int refresh_w,
908		int refresh_h,
909		int draw_x,
910		int draw_y,
911		int draw_w,
912		int draw_h)
913{
914    int const pw = graphic->pixw;
915    int const ph = graphic->pixh;
916    int const graph_x = graphic->charcol * FontWidth(screen);
917    int const graph_y = graphic->charrow * FontHeight(screen);
918    int const graph_w = graphic->actual_width;
919    int const graph_h = graphic->actual_height;
920    int const mw = graphic->max_width;
921    int r, c;
922    int fillx, filly;
923    int holes, total, out_of_range;
924    RegisterNum regnum;
925
926    TRACE(("refreshing graphic %u from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d)\n",
927	   graphic->id,
928	   graph_x, graph_y, draw_w, draw_h,
929	   graphic->valid,
930	   graphic->actual_width,
931	   graphic->actual_height,
932	   pw, ph,
933	   graphic->max_width,
934	   graphic->max_height));
935
936    TRACE(("refresh pixmap starts at %d,%d\n", refresh_x, refresh_y));
937
938    holes = total = 0;
939    out_of_range = 0;
940    for (r = 0; r < graph_h; r++) {
941	int pmy = graph_y + r * ph;
942
943	if (pmy + ph - 1 < draw_y)
944	    continue;
945	if (pmy > draw_y + draw_h - 1)
946	    break;
947
948	for (c = 0; c < graph_w; c++) {
949	    int pmx = graph_x + c * pw;
950
951	    if (pmx + pw - 1 < draw_x)
952		continue;
953	    if (pmx > draw_x + draw_w - 1)
954		break;
955
956	    total++;
957	    regnum = graphic->pixels[r * mw + c];
958	    if (regnum == COLOR_HOLE) {
959		holes++;
960		continue;
961	    }
962
963	    for (fillx = 0; fillx < pw; fillx++) {
964		for (filly = 0; filly < ph; filly++) {
965		    if (pmx < draw_x || pmx > draw_x + draw_w - 1 ||
966			pmy < draw_y || pmy > draw_y + draw_h - 1) {
967			out_of_range++;
968			continue;
969		    }
970
971		    /* this shouldn't happen, but it doesn't hurt to check */
972		    if (pmx < refresh_x || pmx > refresh_x + refresh_w - 1 ||
973			pmy < refresh_y || pmy > refresh_y + refresh_h - 1) {
974			TRACE(("OUT OF RANGE: %d,%d (%d,%d)\n", pmx, pmy, r, c));
975			out_of_range++;
976			continue;
977		    }
978
979		    buffer[(pmy - refresh_y) * refresh_w +
980			   (pmx - refresh_x)] =
981			graphic->color_registers[regnum];
982		}
983	    }
984	}
985    }
986
987    TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes; %d were out of pixmap range\n",
988	   holes, total, out_of_range));
989}
990
991#ifdef DEBUG_REFRESH
992
993#define BASEX(X) ( (draw_x - base_x) + (X) )
994#define BASEY(Y) ( (draw_y - base_y) + (Y) )
995
996static void
997outline_refresh(TScreen const *screen,
998		Graphic const *graphic,
999		Pixmap output_pm,
1000		GC graphics_gc,
1001		int base_x,
1002		int base_y,
1003		int draw_x,
1004		int draw_y,
1005		int draw_w,
1006		int draw_h)
1007{
1008    Display *const display = screen->display;
1009    int const pw = graphic->pixw;
1010    int const ph = graphic->pixh;
1011    XGCValues xgcv;
1012    XColor def;
1013
1014    def.red = (unsigned short) ((1.0 - 0.1 * (rand() / (double)
1015					      RAND_MAX) * 65535.0));
1016    def.green = (unsigned short) ((0.7 + 0.2 * (rand() / (double)
1017						RAND_MAX)) * 65535.0);
1018    def.blue = (unsigned short) ((0.1 + 0.1 * (rand() / (double)
1019					       RAND_MAX)) * 65535.0);
1020    def.flags = DoRed | DoGreen | DoBlue;
1021    if (allocateBestRGB(graphic->xw, &def)) {
1022	xgcv.foreground = def.pixel;
1023	XChangeGC(display, graphics_gc, GCForeground, &xgcv);
1024    }
1025
1026    XDrawLine(display, output_pm, graphics_gc,
1027	      BASEX(0), BASEY(0),
1028	      BASEX(draw_w - 1), BASEY(0));
1029    XDrawLine(display, output_pm, graphics_gc,
1030	      BASEX(0), BASEY(draw_h - 1),
1031	      BASEX(draw_w - 1), BASEY(draw_h - 1));
1032
1033    XDrawLine(display, output_pm, graphics_gc,
1034	      BASEX(0), BASEY(0),
1035	      BASEX(0), BASEY(draw_h - 1));
1036    XDrawLine(display, output_pm, graphics_gc,
1037	      BASEX(draw_w - 1), BASEY(0),
1038	      BASEX(draw_w - 1), BASEY(draw_h - 1));
1039
1040    XDrawLine(display, output_pm, graphics_gc,
1041	      BASEX(draw_w - 1), BASEY(0),
1042	      BASEX(0), BASEY(draw_h - 1));
1043    XDrawLine(display, output_pm, graphics_gc,
1044	      BASEX(draw_w - 1), BASEY(draw_h - 1),
1045	      BASEX(0), BASEY(0));
1046
1047    def.red = (short) (0.7 * 65535.0);
1048    def.green = (short) (0.1 * 65535.0);
1049    def.blue = (short) (1.0 * 65535.0);
1050    def.flags = DoRed | DoGreen | DoBlue;
1051    if (allocateBestRGB(graphic->xw, &def)) {
1052	xgcv.foreground = def.pixel;
1053	XChangeGC(display, graphics_gc, GCForeground, &xgcv);
1054    }
1055    XFillRectangle(display, output_pm, graphics_gc,
1056		   BASEX(0),
1057		   BASEY(0),
1058		   (unsigned) pw, (unsigned) ph);
1059    XFillRectangle(display, output_pm, graphics_gc,
1060		   BASEX(draw_w - 1 - pw),
1061		   BASEY(draw_h - 1 - ph),
1062		   (unsigned) pw, (unsigned) ph);
1063}
1064#endif
1065
1066/*
1067 * Primary color hues:
1068 *  blue:    0 degrees
1069 *  red:   120 degrees
1070 *  green: 240 degrees
1071 */
1072void
1073hls2rgb(int h, int l, int s, short *r, short *g, short *b)
1074{
1075    const int hs = ((h + 240) / 60) % 6;
1076    const double lv = l / 100.0;
1077    const double sv = s / 100.0;
1078    double c, x, m, c2;
1079    double r1, g1, b1;
1080
1081    if (s == 0) {
1082	*r = *g = *b = (short) l;
1083	return;
1084    }
1085
1086    c2 = (2.0 * lv) - 1.0;
1087    if (c2 < 0.0)
1088	c2 = -c2;
1089    c = (1.0 - c2) * sv;
1090    x = (hs & 1) ? c : 0.0;
1091    m = lv - 0.5 * c;
1092
1093    switch (hs) {
1094    case 0:
1095	r1 = c;
1096	g1 = x;
1097	b1 = 0.0;
1098	break;
1099    case 1:
1100	r1 = x;
1101	g1 = c;
1102	b1 = 0.0;
1103	break;
1104    case 2:
1105	r1 = 0.0;
1106	g1 = c;
1107	b1 = x;
1108	break;
1109    case 3:
1110	r1 = 0.0;
1111	g1 = x;
1112	b1 = c;
1113	break;
1114    case 4:
1115	r1 = x;
1116	g1 = 0.0;
1117	b1 = c;
1118	break;
1119    case 5:
1120	r1 = c;
1121	g1 = 0.0;
1122	b1 = x;
1123	break;
1124    default:
1125	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
1126	*r = (short) 100;
1127	*g = (short) 100;
1128	*b = (short) 100;
1129	return;
1130    }
1131
1132    *r = (short) ((r1 + m) * 100.0 + 0.5);
1133    *g = (short) ((g1 + m) * 100.0 + 0.5);
1134    *b = (short) ((b1 + m) * 100.0 + 0.5);
1135
1136    if (*r < 0)
1137	*r = 0;
1138    else if (*r > 100)
1139	*r = 100;
1140    if (*g < 0)
1141	*g = 0;
1142    else if (*g > 100)
1143	*g = 100;
1144    if (*b < 0)
1145	*b = 0;
1146    else if (*b > 100)
1147	*b = 100;
1148}
1149
1150void
1151dump_graphic(Graphic const *graphic)
1152{
1153#if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
1154    RegisterNum color;
1155#endif
1156#ifdef DUMP_BITMAP
1157    int r, c;
1158    ColorRegister const *reg;
1159#endif
1160
1161    (void) graphic;
1162
1163    TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
1164	   graphic->id,
1165	   graphic->charrow,
1166	   graphic->charcol,
1167	   graphic->actual_width,
1168	   graphic->actual_height,
1169	   graphic->pixw,
1170	   graphic->pixh));
1171
1172#ifdef DUMP_COLORS
1173    TRACE(("graphic colors:\n"));
1174    for (color = 0; color < graphic->valid_registers; color++) {
1175	TRACE(("%03u: %d,%d,%d\n",
1176	       color,
1177	       graphic->color_registers[color].r,
1178	       graphic->color_registers[color].g,
1179	       graphic->color_registers[color].b));
1180    }
1181#endif
1182
1183#ifdef DUMP_BITMAP
1184    TRACE(("graphic pixels:\n"));
1185    for (r = 0; r < graphic->actual_height; r++) {
1186	for (c = 0; c < graphic->actual_width; c++) {
1187	    color = graphic->pixels[r * graphic->max_width + c];
1188	    if (color == COLOR_HOLE) {
1189		TRACE(("?"));
1190	    } else {
1191		reg = &graphic->color_registers[color];
1192		if (reg->r + reg->g + reg->b > 200) {
1193		    TRACE(("#"));
1194		} else if (reg->r + reg->g + reg->b > 150) {
1195		    TRACE(("%%"));
1196		} else if (reg->r + reg->g + reg->b > 100) {
1197		    TRACE((":"));
1198		} else if (reg->r + reg->g + reg->b > 80) {
1199		    TRACE(("."));
1200		} else {
1201		    TRACE((" "));
1202		}
1203	    }
1204	}
1205	TRACE(("\n"));
1206    }
1207
1208    TRACE(("\n"));
1209#endif
1210}
1211
1212/* Erase the portion of any displayed graphic overlapping with a rectangle
1213 * of the given size and location in pixels relative to the start of the
1214 * graphic.  This is used to allow text to "erase" graphics underneath it.
1215 */
1216static void
1217erase_graphic(Graphic *graphic, int x, int y, int w, int h)
1218{
1219    RegisterNum hole = COLOR_HOLE;
1220    int pw, ph;
1221    int r, c;
1222    int rbase, cbase;
1223
1224    pw = graphic->pixw;
1225    ph = graphic->pixh;
1226
1227    TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
1228
1229    rbase = 0;
1230    for (r = 0; r < graphic->actual_height; r++) {
1231	if (rbase + ph - 1 >= y
1232	    && rbase <= y + h - 1) {
1233	    cbase = 0;
1234	    for (c = 0; c < graphic->actual_width; c++) {
1235		if (cbase + pw - 1 >= x
1236		    && cbase <= x + w - 1) {
1237		    graphic->pixels[r * graphic->max_width + c] = hole;
1238		}
1239		cbase += pw;
1240	    }
1241	}
1242	rbase += ph;
1243    }
1244}
1245
1246static int
1247compare_graphic_ids(const void *left, const void *right)
1248{
1249    const Graphic *l = *(const Graphic *const *) left;
1250    const Graphic *r = *(const Graphic *const *) right;
1251
1252    if (!l->valid || !r->valid)
1253	return 0;
1254
1255    if (l->bufferid < r->bufferid)
1256	return -1;
1257    else if (l->bufferid > r->bufferid)
1258	return 1;
1259
1260    if (l->id < r->id)
1261	return -1;
1262    else
1263	return 1;
1264}
1265
1266static void
1267clip_area(int *orig_x, int *orig_y, int *orig_w, int *orig_h,
1268	  int clip_x, int clip_y, int clip_w, int clip_h)
1269{
1270    if (*orig_x < clip_x) {
1271	const int diff = clip_x - *orig_x;
1272	*orig_x += diff;
1273	*orig_w -= diff;
1274    }
1275    if (*orig_w > 0 && *orig_x + *orig_w > clip_x + clip_w) {
1276	*orig_w -= (*orig_x + *orig_w) - (clip_x + clip_w);
1277    }
1278
1279    if (*orig_y < clip_y) {
1280	const int diff = clip_y - *orig_y;
1281	*orig_y += diff;
1282	*orig_h -= diff;
1283    }
1284    if (*orig_h > 0 && *orig_y + *orig_h > clip_y + clip_h) {
1285	*orig_h -= (*orig_y + *orig_h) - (clip_y + clip_h);
1286    }
1287}
1288
1289/* the coordinates are relative to the screen */
1290static void
1291refresh_graphics(XtermWidget xw,
1292		 int leftcol,
1293		 int toprow,
1294		 int ncols,
1295		 int nrows,
1296		 int skip_clean)
1297{
1298    TScreen *const screen = TScreenOf(xw);
1299    Display *const display = screen->display;
1300    Window const drawable = VDrawable(screen);
1301    int const scroll_y = screen->topline * FontHeight(screen);
1302    int const refresh_x = leftcol * FontWidth(screen);
1303    int const refresh_y = toprow * FontHeight(screen) + scroll_y;
1304    int const refresh_w = ncols * FontWidth(screen);
1305    int const refresh_h = nrows * FontHeight(screen);
1306    int draw_x_min, draw_x_max;
1307    int draw_y_min, draw_y_max;
1308    Graphic *ordered_graphics[MAX_GRAPHICS];
1309    unsigned ii, jj;
1310    unsigned active_count;
1311    unsigned holes, non_holes;
1312    int xx, yy;
1313    ColorRegister *buffer;
1314
1315    active_count = 0;
1316    FOR_EACH_SLOT(ii) {
1317	Graphic *graphic;
1318	if (!(graphic = getActiveSlot(ii)))
1319	    continue;
1320	TRACE(("refreshing graphic %d on buffer %d, current buffer %d\n",
1321	       graphic->id, graphic->bufferid, screen->whichBuf));
1322	if (screen->whichBuf == 0) {
1323	    if (graphic->bufferid != 0) {
1324		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d\n",
1325		       graphic->id, graphic->bufferid, screen->whichBuf));
1326		continue;
1327	    }
1328	} else {
1329	    if (graphic->bufferid == 0 && graphic->charrow >= 0) {
1330		TRACE(("skipping graphic %d from normal buffer (%d) when drawing screen=%d because it is not in scrollback area\n",
1331		       graphic->id, graphic->bufferid, screen->whichBuf));
1332		continue;
1333	    }
1334	    if (graphic->bufferid == 1 &&
1335		graphic->charrow + (graphic->actual_height +
1336				    FontHeight(screen) - 1) /
1337		FontHeight(screen) < 0) {
1338		TRACE(("skipping graphic %d from alt buffer (%d) when drawing screen=%d because it is completely in scrollback area\n",
1339		       graphic->id, graphic->bufferid, screen->whichBuf));
1340		continue;
1341	    }
1342	}
1343	ordered_graphics[active_count++] = graphic;
1344    }
1345
1346    if (active_count == 0)
1347	return;
1348    if (active_count > 1) {
1349	qsort(ordered_graphics,
1350	      (size_t) active_count,
1351	      sizeof(ordered_graphics[0]),
1352	      compare_graphic_ids);
1353    }
1354
1355    if (skip_clean) {
1356	unsigned skip_count;
1357
1358	for (jj = 0; jj < active_count; ++jj) {
1359	    if (ordered_graphics[jj]->dirty)
1360		break;
1361	}
1362	skip_count = jj;
1363	if (skip_count == active_count)
1364	    return;
1365
1366	active_count -= skip_count;
1367	for (jj = 0; jj < active_count; ++jj) {
1368	    ordered_graphics[jj] = ordered_graphics[jj + skip_count];
1369	}
1370    }
1371
1372    if (!(buffer = malloc(sizeof(*buffer) *
1373			  (unsigned) refresh_w * (unsigned) refresh_h))) {
1374	TRACE(("unable to allocate %dx%d buffer for graphics refresh\n",
1375	       refresh_w, refresh_h));
1376	return;
1377    }
1378    for (yy = 0; yy < refresh_h; yy++) {
1379	for (xx = 0; xx < refresh_w; xx++) {
1380	    buffer[yy * refresh_w + xx].r = -1;
1381	    buffer[yy * refresh_w + xx].g = -1;
1382	    buffer[yy * refresh_w + xx].b = -1;
1383	}
1384    }
1385
1386    TRACE(("refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d (%d,%d %dx%d)\n",
1387	   screen->topline,
1388	   leftcol, toprow,
1389	   nrows, ncols,
1390	   refresh_x, refresh_y,
1391	   refresh_w, refresh_h));
1392
1393    {
1394	int const altarea_x = 0;
1395	int const altarea_y = 0;
1396	int const altarea_w = Width(screen) * FontWidth(screen);
1397	int const altarea_h = Height(screen) * FontHeight(screen);
1398
1399	int const scrollarea_x = 0;
1400	int const scrollarea_y = scroll_y;
1401	int const scrollarea_w = Width(screen) * FontWidth(screen);
1402	int const scrollarea_h = -scroll_y;
1403
1404	int const mainarea_x = 0;
1405	int const mainarea_y = scroll_y;
1406	int const mainarea_w = Width(screen) * FontWidth(screen);
1407	int const mainarea_h = -scroll_y + Height(screen) * FontHeight(screen);
1408
1409	draw_x_min = refresh_x + refresh_w;
1410	draw_x_max = refresh_x - 1;
1411	draw_y_min = refresh_y + refresh_h;
1412	draw_y_max = refresh_y - 1;
1413	for (jj = 0; jj < active_count; ++jj) {
1414	    Graphic *graphic = ordered_graphics[jj];
1415	    int draw_x = graphic->charcol * FontWidth(screen);
1416	    int draw_y = graphic->charrow * FontHeight(screen);
1417	    int draw_w = graphic->actual_width;
1418	    int draw_h = graphic->actual_height;
1419
1420	    if (screen->whichBuf != 0) {
1421		if (graphic->bufferid != 0) {
1422		    /* clip to alt buffer */
1423		    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
1424			      altarea_x, altarea_y, altarea_w, altarea_h);
1425		} else if (graphic->bufferid == 0) {
1426		    /* clip to scrollback area */
1427		    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
1428			      scrollarea_x, scrollarea_y,
1429			      scrollarea_w, scrollarea_h);
1430		}
1431	    } else {
1432		/* clip to scrollback + normal area */
1433		clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
1434			  mainarea_x, mainarea_y,
1435			  mainarea_w, mainarea_h);
1436	    }
1437
1438	    clip_area(&draw_x, &draw_y, &draw_w, &draw_h,
1439		      refresh_x, refresh_y, refresh_w, refresh_h);
1440
1441	    TRACE(("refresh: graph=%u\n", jj));
1442	    TRACE(("         refresh_x=%d refresh_y=%d refresh_w=%d refresh_h=%d\n",
1443		   refresh_x, refresh_y, refresh_w, refresh_h));
1444	    TRACE(("         draw_x=%d draw_y=%d draw_w=%d draw_h=%d\n",
1445		   draw_x, draw_y, draw_w, draw_h));
1446
1447	    if (draw_w > 0 && draw_h > 0) {
1448		refresh_graphic(screen, graphic, buffer,
1449				refresh_x, refresh_y,
1450				refresh_w, refresh_h,
1451				draw_x, draw_y,
1452				draw_w, draw_h);
1453		if (draw_x < draw_x_min)
1454		    draw_x_min = draw_x;
1455		if (draw_x + draw_w - 1 > draw_x_max)
1456		    draw_x_max = draw_x + draw_w - 1;
1457		if (draw_y < draw_y_min)
1458		    draw_y_min = draw_y;
1459		if (draw_y + draw_h - 1 > draw_y_max)
1460		    draw_y_max = draw_y + draw_h - 1;
1461	    }
1462	    graphic->dirty = 0;
1463	}
1464    }
1465
1466    if (draw_x_max < refresh_x ||
1467	draw_x_min > refresh_x + refresh_w - 1 ||
1468	draw_y_max < refresh_y ||
1469	draw_y_min > refresh_y + refresh_h - 1) {
1470	free(buffer);
1471	return;
1472    }
1473
1474    holes = 0U;
1475    non_holes = 0U;
1476    for (yy = draw_y_min - refresh_y; yy <= draw_y_max - refresh_y; yy++) {
1477	for (xx = draw_x_min - refresh_x; xx <= draw_x_max - refresh_x; xx++) {
1478	    const ColorRegister color = buffer[yy * refresh_w + xx];
1479	    if (color.r < 0 || color.g < 0 || color.b < 0) {
1480		holes++;
1481	    } else {
1482		non_holes++;
1483	    }
1484	}
1485    }
1486
1487    if (non_holes < 1U) {
1488	TRACE(("refresh: visible graphics areas are erased; nothing to do\n"));
1489	free(buffer);
1490	return;
1491    }
1492
1493    /*
1494     * If we have any holes we can't just copy an image rectangle, and masking
1495     * with bitmaps is very expensive.  This fallback is surprisingly faster
1496     * than the XPutImage version in some cases, but I don't know why.
1497     * (This is even though there's no X11 primitive for drawing a horizontal
1498     * line of height one and no attempt is made to handle multiple lines at
1499     * once.)
1500     */
1501    if (holes > 0U) {
1502	GC graphics_gc;
1503	XGCValues xgcv;
1504	ColorRegister last_color;
1505	ColorRegister gc_color;
1506	int run;
1507
1508	memset(&xgcv, 0, sizeof(xgcv));
1509	xgcv.graphics_exposures = False;
1510	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
1511	if (graphics_gc == None) {
1512	    TRACE(("unable to allocate GC for graphics refresh\n"));
1513	    free(buffer);
1514	    return;
1515	}
1516
1517	last_color.r = -1;
1518	last_color.g = -1;
1519	last_color.b = -1;
1520	gc_color.r = -1;
1521	gc_color.g = -1;
1522	gc_color.b = -1;
1523	run = 0;
1524	for (yy = draw_y_min - refresh_y; yy <= draw_y_max - refresh_y; yy++) {
1525	    for (xx = draw_x_min - refresh_x; xx <= draw_x_max - refresh_x;
1526		 xx++) {
1527		const ColorRegister color = buffer[yy * refresh_w + xx];
1528
1529		if (color.r < 0 || color.g < 0 || color.b < 0) {
1530		    last_color = color;
1531		    if (run > 0) {
1532			XDrawLine(display, drawable, graphics_gc,
1533				  OriginX(screen) + refresh_x + xx - run,
1534				  (OriginY(screen) - scroll_y) + refresh_y + yy,
1535				  OriginX(screen) + refresh_x + xx - 1,
1536				  (OriginY(screen) - scroll_y) + refresh_y + yy);
1537			run = 0;
1538		    }
1539		    continue;
1540		}
1541
1542		if (color.r != last_color.r ||
1543		    color.g != last_color.g ||
1544		    color.b != last_color.b) {
1545		    last_color = color;
1546		    if (run > 0) {
1547			XDrawLine(display, drawable, graphics_gc,
1548				  OriginX(screen) + refresh_x + xx - run,
1549				  (OriginY(screen) - scroll_y) + refresh_y + yy,
1550				  OriginX(screen) + refresh_x + xx - 1,
1551				  (OriginY(screen) - scroll_y) + refresh_y + yy);
1552			run = 0;
1553		    }
1554
1555		    if (color.r != gc_color.r ||
1556			color.g != gc_color.g ||
1557			color.b != gc_color.b) {
1558			xgcv.foreground =
1559			    color_register_to_xpixel(&color, xw);
1560			XChangeGC(display, graphics_gc, GCForeground, &xgcv);
1561			gc_color = color;
1562		    }
1563		}
1564		run++;
1565	    }
1566	    if (run > 0) {
1567		last_color.r = -1;
1568		last_color.g = -1;
1569		last_color.b = -1;
1570		XDrawLine(display, drawable, graphics_gc,
1571			  OriginX(screen) + refresh_x + xx - run,
1572			  (OriginY(screen) - scroll_y) + refresh_y + yy,
1573			  OriginX(screen) + refresh_x + xx - 1,
1574			  (OriginY(screen) - scroll_y) + refresh_y + yy);
1575		run = 0;
1576	    }
1577	}
1578
1579	XFreeGC(display, graphics_gc);
1580    } else {
1581	XGCValues xgcv;
1582	GC graphics_gc;
1583	ColorRegister old_color;
1584	Pixel fg;
1585	XImage *image;
1586	char *imgdata;
1587	unsigned image_w, image_h;
1588
1589	memset(&xgcv, 0, sizeof(xgcv));
1590	xgcv.graphics_exposures = False;
1591	graphics_gc = XCreateGC(display, drawable, GCGraphicsExposures, &xgcv);
1592	if (graphics_gc == None) {
1593	    TRACE(("unable to allocate GC for graphics refresh\n"));
1594	    free(buffer);
1595	    return;
1596	}
1597
1598	/* FIXME: is it worth reusing the GC/Image/imagedata across calls? */
1599	/* FIXME: is it worth using shared memory when available? */
1600	image_w = (unsigned) draw_x_max + 1U - (unsigned) draw_x_min;
1601	image_h = (unsigned) draw_y_max + 1U - (unsigned) draw_y_min;
1602	image = XCreateImage(display, xw->visInfo->visual,
1603			     (unsigned) xw->visInfo->depth,
1604			     ZPixmap, 0, NULL,
1605			     image_w, image_h,
1606			     sizeof(int) * 8U, 0);
1607	if (!image) {
1608	    TRACE(("unable to allocate XImage for graphics refresh\n"));
1609	    XFreeGC(display, graphics_gc);
1610	    free(buffer);
1611	    return;
1612	}
1613	imgdata = malloc(image_h * (unsigned) image->bytes_per_line);
1614	if (!imgdata) {
1615	    TRACE(("unable to allocate XImage for graphics refresh\n"));
1616	    XDestroyImage(image);
1617	    XFreeGC(display, graphics_gc);
1618	    free(buffer);
1619	    return;
1620	}
1621	image->data = imgdata;
1622
1623	fg = 0U;
1624	old_color.r = -1;
1625	old_color.g = -1;
1626	old_color.b = -1;
1627	for (yy = draw_y_min - refresh_y; yy <= draw_y_max - refresh_y; yy++) {
1628	    for (xx = draw_x_min - refresh_x; xx <= draw_x_max - refresh_x;
1629		 xx++) {
1630		const ColorRegister color = buffer[yy * refresh_w + xx];
1631
1632		if (color.r != old_color.r ||
1633		    color.g != old_color.g ||
1634		    color.b != old_color.b) {
1635		    fg = color_register_to_xpixel(&color, xw);
1636		    old_color = color;
1637		}
1638
1639		XPutPixel(image, xx + refresh_x - draw_x_min,
1640			  yy + refresh_y - draw_y_min, fg);
1641	    }
1642	}
1643
1644	XPutImage(display, drawable, graphics_gc, image,
1645		  0, 0,
1646		  OriginX(screen) + draw_x_min,
1647		  (OriginY(screen) - scroll_y) + draw_y_min,
1648		  image_w, image_h);
1649	free(imgdata);
1650	image->data = NULL;
1651	XDestroyImage(image);
1652	XFreeGC(display, graphics_gc);
1653    }
1654
1655    free(buffer);
1656    XFlush(display);
1657}
1658
1659void
1660refresh_displayed_graphics(XtermWidget xw,
1661			   int leftcol,
1662			   int toprow,
1663			   int ncols,
1664			   int nrows)
1665{
1666    refresh_graphics(xw, leftcol, toprow, ncols, nrows, 0);
1667}
1668
1669void
1670refresh_modified_displayed_graphics(XtermWidget xw)
1671{
1672    TScreen const *screen = TScreenOf(xw);
1673    refresh_graphics(xw, 0, 0, MaxCols(screen), MaxRows(screen), 1);
1674}
1675
1676void
1677scroll_displayed_graphics(XtermWidget xw, int rows)
1678{
1679    TScreen const *screen = TScreenOf(xw);
1680    Graphic *graphic;
1681    unsigned ii;
1682
1683    TRACE(("graphics scroll: moving all up %d rows\n", rows));
1684    /* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
1685
1686    FOR_EACH_SLOT(ii) {
1687	if (!(graphic = getActiveSlot(ii)))
1688	    continue;
1689	if (graphic->bufferid != screen->whichBuf)
1690	    continue;
1691
1692	graphic->charrow -= rows;
1693    }
1694}
1695
1696void
1697pixelarea_clear_displayed_graphics(TScreen const *screen,
1698				   int winx,
1699				   int winy,
1700				   int w,
1701				   int h)
1702{
1703    unsigned ii;
1704
1705    FOR_EACH_SLOT(ii) {
1706	Graphic *graphic;
1707	/* FIXME: are these coordinates (scrolled) screen-relative? */
1708	int const scroll_y = (screen->whichBuf == 0
1709			      ? screen->topline * FontHeight(screen)
1710			      : 0);
1711	int graph_x;
1712	int graph_y;
1713	int x, y;
1714
1715	if (!(graphic = getActiveSlot(ii)))
1716	    continue;
1717	if (graphic->bufferid != screen->whichBuf)
1718	    continue;
1719
1720	graph_x = graphic->charcol * FontWidth(screen);
1721	graph_y = graphic->charrow * FontHeight(screen);
1722	x = winx - graph_x;
1723	y = (winy - scroll_y) - graph_y;
1724
1725	TRACE(("pixelarea clear graphics: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
1726	       screen->topline,
1727	       winx, winy,
1728	       w, h,
1729	       x, y));
1730	erase_graphic(graphic, x, y, w, h);
1731    }
1732}
1733
1734void
1735chararea_clear_displayed_graphics(TScreen const *screen,
1736				  int leftcol,
1737				  int toprow,
1738				  int ncols,
1739				  int nrows)
1740{
1741    int const x = leftcol * FontWidth(screen);
1742    int const y = toprow * FontHeight(screen);
1743    int const w = ncols * FontWidth(screen);
1744    int const h = nrows * FontHeight(screen);
1745
1746    TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
1747	   screen->topline,
1748	   leftcol, toprow,
1749	   nrows, ncols,
1750	   x, y, w, h));
1751    pixelarea_clear_displayed_graphics(screen, x, y, w, h);
1752}
1753
1754void
1755reset_displayed_graphics(TScreen const *screen)
1756{
1757    unsigned ii;
1758
1759    init_color_registers(getSharedRegisters(), screen->terminal_id);
1760
1761    TRACE(("resetting all graphics\n"));
1762    FOR_EACH_SLOT(ii) {
1763	deactivateSlot(ii);
1764    }
1765}
1766
1767#ifdef NO_LEAKS
1768void
1769noleaks_graphics(void)
1770{
1771    unsigned ii;
1772
1773    FOR_EACH_SLOT(ii) {
1774	deactivateSlot(ii);
1775    }
1776}
1777#endif
1778