graphics.c revision 894e0ac8
1/* $XTermId: graphics.c,v 1.43 2014/05/28 22:27:07 tom Exp $ */
2
3/*
4 * Copyright 2013,2014 by Ross Combs
5 *
6 *                         All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * 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/* TODO:
52 * ReGIS:
53 * - shading with text
54 * - polygon filling
55 * - plane write control
56 * - fix interpolated curves to more closely match implementation (identical despite direction and starting point)
57 * - text
58 * - input and output cursors
59 * - mouse input
60 * - stacks
61 * - investigate second graphic page for ReGIS -- does it also apply to text and sixel graphics? are the contents preserved?
62 * - font upload, italics, and other text attributes
63 * - enter/leave during a command
64 * - command display mode
65 * - scrolling
66 * - custom coordinate systems
67 * - scaling/re-rasterization to fit screen
68 * - macros
69 * sixel:
70 * - fix problem where new_row < 0 during sixel parsing (see FIXME)
71 * VT55/VT105 waveform graphics
72 * - everything
73 * common:
74 * - handle light/dark screen modes (CSI?5[hl])
75 * - update text fg/bg color which overlaps images
76 * - erase graphic when erasing screen
77 * - handle graphic updates in scroll regions
78 * - handle rectangular area copies (verify they work with graphics)
79 * - maintain ordered list/array instead of qsort()
80 * - erase text under graphic if bg not transparent to avoid flickering (or not: bad if the font changes or window resizes)
81 * - erase graphics under graphic if same origin and bg not transparent to avoid flickering
82 * - erase scrolled portions of all graphics on alt buffer
83 * - delete graphic if scrolled past end of scrollback
84 * - delete graphic if all pixels are transparent/erased
85 * - dynamic memory allocation of graphics buffers, add configurable limits
86 * - auto convert color graphics in VT330 mode
87 * - posturize requested colors to match hardware palettes (e.g. four possible shades on VT240)
88 * - color register report/restore
89 * escape sequences:
90 * - way to query font size without "window ops" (or make "window ops" permissions more fine grained)
91 * - way to query and/or set the maximum number of color registers
92 * - way to query and set the number of graphics pages
93 * ReGIS extensions:
94 * - gradients
95 * - line width (RLogin has this and it is mentioned in docs for the DEC ReGIS to Postscript converter)
96 * - F option for screen command (mentioned in docs for the DEC ReGIS to Postscript converter)
97 * - transparency
98 * - background color as stackable write control
99 * - RGB triplets
100 * - true color (virtual color registers created upon lookup)
101 * - anti-aliasing
102 */
103
104/* font sizes:
105 * VT510:
106 *   80 Columns 132 Columns Maximum Number of Lines
107 *   10 x 16   6 x 16  26 lines + keyboard indicator line
108 *   10 x 13   6 x 13  26 lines + keyboard indicator line
109 *   10 x 10   6 x 10  42 lines + keyboard indicator line
110 *   10 x 8    6 x 8   53 lines + keyboard indicator line
111*/
112
113#define FOR_EACH_SLOT(ii) for (ii = 0U; ii < MAX_GRAPHICS; ii++)
114
115static ColorRegister *shared_color_registers;
116static Graphic *displayed_graphics[MAX_GRAPHICS];
117static unsigned next_graphic_id = 0U;
118
119static ColorRegister *
120allocRegisters(void)
121{
122    return TypeCallocN(ColorRegister, MAX_COLOR_REGISTERS);
123}
124
125static Graphic *
126freeGraphic(Graphic *obj)
127{
128    if (obj) {
129	if (obj->pixels)
130	    free(obj->pixels);
131	if (obj->private_color_registers)
132	    free(obj->private_color_registers);
133	free(obj);
134    }
135    return NULL;
136}
137
138static Graphic *
139allocGraphic(void)
140{
141    Graphic *result = TypeCalloc(Graphic);
142    if (result) {
143	if (!(result->pixels = TypeCallocN(RegisterNum, MAX_PIXELS))) {
144	    result = freeGraphic(result);
145	} else if (!(result->private_color_registers = allocRegisters())) {
146	    result = freeGraphic(result);
147	}
148    }
149    return result;
150}
151
152static Graphic *
153getActiveSlot(unsigned n)
154{
155    if (n < MAX_GRAPHICS &&
156	displayed_graphics[n] &&
157	displayed_graphics[n]->valid) {
158	return displayed_graphics[n];
159    }
160    return NULL;
161}
162
163static Graphic *
164getInactiveSlot(unsigned n)
165{
166    if (n < MAX_GRAPHICS &&
167	(!displayed_graphics[n] ||
168	 !displayed_graphics[n]->valid)) {
169	if (!displayed_graphics[n]) {
170	    displayed_graphics[n] = allocGraphic();
171	}
172	return displayed_graphics[n];
173    }
174    return NULL;
175}
176
177static ColorRegister *
178getSharedRegisters(void)
179{
180    if (!shared_color_registers)
181	shared_color_registers = allocRegisters();
182    return shared_color_registers;
183}
184
185static void
186deactivateSlot(unsigned n)
187{
188    if (n < MAX_GRAPHICS) {
189	displayed_graphics[n] = freeGraphic(displayed_graphics[n]);
190    }
191}
192
193extern RegisterNum
194read_pixel(Graphic *graphic, int x, int y)
195{
196    if (x < 0 && x >= graphic->actual_width &&
197	y < 0 && y >= graphic->actual_height) {
198	return COLOR_HOLE;
199    }
200
201    return graphic->pixels[y * graphic->max_width + x];
202}
203
204void
205draw_solid_pixel(Graphic *graphic, int x, int y, unsigned color)
206{
207    assert(color <= MAX_COLOR_REGISTERS);
208
209#ifdef DEBUG_PIXEL
210    TRACE(("drawing pixel at %d,%d color=%hu (hole=%hu, [%d,%d,%d])\n",
211	   x,
212	   y,
213	   color,
214	   COLOR_HOLE,
215	   ((color != COLOR_HOLE)
216	    ? (unsigned) graphic->color_registers[color].r : 0U),
217	   ((color != COLOR_HOLE)
218	    ? (unsigned) graphic->color_registers[color].g : 0U),
219	   ((color != COLOR_HOLE)
220	    ? (unsigned) graphic->color_registers[color].b : 0U)));
221#endif
222    if (x >= 0 && x < graphic->actual_width &&
223	y >= 0 && y < graphic->actual_height) {
224	graphic->pixels[y * graphic->max_width + x] = (RegisterNum) color;
225	if (color < MAX_COLOR_REGISTERS)
226	    graphic->color_registers_used[color] = 1;
227    } else {
228	TRACE(("pixel %d,%d out of bounds\n", x, y));
229    }
230}
231
232void
233draw_solid_rectangle(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
234{
235    int x, y;
236    int tmp;
237
238    assert(color <= MAX_COLOR_REGISTERS);
239
240    if (x1 > x2) {
241	EXCHANGE(x1, x2, tmp);
242    }
243    if (y1 > y2) {
244	EXCHANGE(y1, y2, tmp);
245    }
246
247    for (y = y1; y <= y2; y++)
248	for (x = x1; x < x2; x++)
249	    draw_solid_pixel(graphic, x, y, color);
250}
251
252void
253draw_solid_line(Graphic *graphic, int x1, int y1, int x2, int y2, unsigned color)
254{
255    int x, y;
256    int dx, dy;
257    int dir, diff;
258
259    assert(color <= MAX_COLOR_REGISTERS);
260
261    dx = abs(x1 - x2);
262    dy = abs(y1 - y2);
263
264    if (dx > dy) {
265	if (x1 > x2) {
266	    int tmp;
267	    EXCHANGE(x1, x2, tmp);
268	    EXCHANGE(y1, y2, tmp);
269	}
270	if (y1 < y2)
271	    dir = 1;
272	else if (y1 > y2)
273	    dir = -1;
274	else
275	    dir = 0;
276
277	diff = 0;
278	y = y1;
279	for (x = x1; x <= x2; x++) {
280	    if (diff >= dx) {
281		diff -= dx;
282		y += dir;
283	    }
284	    diff += dy;
285	    draw_solid_pixel(graphic, x, y, color);
286	}
287    } else {
288	if (y1 > y2) {
289	    int tmp;
290	    EXCHANGE(x1, x2, tmp);
291	    EXCHANGE(y1, y2, tmp);
292	}
293	if (x1 < x2)
294	    dir = 1;
295	else if (x1 > x2)
296	    dir = -1;
297	else
298	    dir = 0;
299
300	diff = 0;
301	x = x1;
302	for (y = y1; y <= y2; y++) {
303	    if (diff >= dy) {
304		diff -= dy;
305		x += dir;
306	    }
307	    diff += dx;
308	    draw_solid_pixel(graphic, x, y, color);
309	}
310    }
311}
312
313static void
314set_color_register(ColorRegister *color_registers,
315		   unsigned color,
316		   int r,
317		   int g,
318		   int b)
319{
320    ColorRegister *reg = &color_registers[color];
321    reg->r = (short) r;
322    reg->g = (short) g;
323    reg->b = (short) b;
324    reg->allocated = 0;
325}
326
327/* Graphics which don't use private colors will act as if they are using a
328 * device-wide color palette.
329 */
330static void
331set_shared_color_register(unsigned color, int r, int g, int b)
332{
333    Graphic *graphic;
334    unsigned ii;
335
336    assert(color < MAX_COLOR_REGISTERS);
337
338    set_color_register(getSharedRegisters(), color, r, g, b);
339
340    FOR_EACH_SLOT(ii) {
341	if (!(graphic = getActiveSlot(ii)))
342	    continue;
343	if (graphic->private_colors)
344	    continue;
345
346	if (graphic->color_registers_used[ii]) {
347	    graphic->dirty = 1;
348	}
349    }
350}
351
352void
353update_color_register(Graphic *graphic,
354		      unsigned color,
355		      int r,
356		      int g,
357		      int b)
358{
359    assert(color < MAX_COLOR_REGISTERS);
360
361    if (graphic->private_colors) {
362	set_color_register(graphic->private_color_registers,
363			   color, r, g, b);
364	if (graphic->color_registers_used[color]) {
365	    graphic->dirty = 1;
366	}
367	graphic->color_registers_used[color] = 1;
368    } else {
369	set_shared_color_register(color, r, g, b);
370    }
371}
372
373#define SQUARE(X) ( (X) * (X) )
374
375RegisterNum
376find_color_register(ColorRegister const *color_registers, int r, int g, int b)
377{
378    unsigned i;
379    unsigned d;
380    unsigned closest_index;
381    unsigned closest_distance;
382
383    /* I have no idea what algorithm DEC used for this.
384     * The documentation warns that it is unpredictable, especially with values
385     * far away from any allocated color so it is probably a very simple
386     * hueristic rather than something fancy like finding the minimum distance
387     * in a linear perceptive color space.
388     */
389    closest_index = MAX_COLOR_REGISTERS;
390    closest_distance = 0U;
391    for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
392	d = (unsigned) (SQUARE(2 * (color_registers[i].r - r)) +
393			SQUARE(3 * (color_registers[i].g - g)) +
394			SQUARE(1 * (color_registers[i].b - b)));
395	if (closest_index == MAX_COLOR_REGISTERS || d < closest_distance) {
396	    closest_index = i;
397	    closest_distance = d;
398	}
399    }
400
401    TRACE(("found closest color register to %d,%d,%d: %u (distance %u value %d,%d,%d)\n",
402	   r, g, b,
403	   closest_index,
404	   closest_distance,
405	   color_registers[closest_index].r,
406	   color_registers[closest_index].g,
407	   color_registers[closest_index].b));
408    return (RegisterNum) closest_index;
409}
410
411static void
412init_color_registers(ColorRegister *color_registers, int terminal_id)
413{
414    TRACE(("setting initial colors for terminal %d\n", terminal_id));
415    {
416	unsigned i;
417
418	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
419	    set_color_register(color_registers, (RegisterNum) i, 0, 0, 0);
420	}
421    }
422
423    /*
424     * default color registers:
425     *     (mono) (color)
426     * VK100/GIGI (fixed)
427     * VT125:
428     *   0: 0%      0%
429     *   1: 33%     blue
430     *   2: 66%     red
431     *   3: 100%    green
432     * VT240:
433     *   0: 0%      0%
434     *   1: 33%     blue
435     *   2: 66%     red
436     *   3: 100%    green
437     * VT241:
438     *   0: 0%      0%
439     *   1: 33%     blue
440     *   2: 66%     red
441     *   3: 100%    green
442     * VT330:
443     *   0: 0%      0%              (bg for light on dark mode)
444     *   1: 33%     blue (red?)
445     *   2: 66%     red (green?)
446     *   3: 100%    green (yellow?) (fg for light on dark mode)
447     * VT340:
448     *   0: 0%      0%              (bg for light on dark mode)
449     *   1: 14%     blue
450     *   2: 29%     red
451     *   3: 43%     green
452     *   4: 57%     magenta
453     *   5: 71%     cyan
454     *   6: 86%     yellow
455     *   7: 100%    50%             (fg for light on dark mode)
456     *   8: 0%      25%
457     *   9: 14%     gray-blue
458     *  10: 29%     gray-red
459     *  11: 43%     gray-green
460     *  12: 57%     gray-magenta
461     *  13: 71%     gray-cyan
462     *  14: 86%     gray-yellow
463     *  15: 100%    75%             ("white")
464     * VT382:
465     *   ? (FIXME: B&W only?)
466     * dxterm:
467     *  ?
468     */
469    switch (terminal_id) {
470    case 125:
471    case 241:
472	set_color_register(color_registers, 0, 0, 0, 0);
473	set_color_register(color_registers, 1, 0, 0, 100);
474	set_color_register(color_registers, 2, 0, 100, 0);
475	set_color_register(color_registers, 3, 100, 0, 0);
476	break;
477    case 240:
478    case 330:
479	set_color_register(color_registers, 0, 0, 0, 0);
480	set_color_register(color_registers, 1, 33, 33, 33);
481	set_color_register(color_registers, 2, 66, 66, 66);
482	set_color_register(color_registers, 3, 100, 100, 100);
483	break;
484    case 340:
485    default:
486	set_color_register(color_registers, 0, 0, 0, 0);
487	set_color_register(color_registers, 1, 20, 20, 80);
488	set_color_register(color_registers, 2, 80, 13, 13);
489	set_color_register(color_registers, 3, 20, 80, 20);
490	set_color_register(color_registers, 4, 80, 20, 80);
491	set_color_register(color_registers, 5, 20, 80, 80);
492	set_color_register(color_registers, 6, 80, 80, 20);
493	set_color_register(color_registers, 7, 53, 53, 53);
494	set_color_register(color_registers, 8, 26, 26, 26);
495	set_color_register(color_registers, 9, 33, 33, 60);
496	set_color_register(color_registers, 10, 60, 26, 26);
497	set_color_register(color_registers, 11, 33, 60, 33);
498	set_color_register(color_registers, 12, 60, 33, 60);
499	set_color_register(color_registers, 13, 33, 60, 60);
500	set_color_register(color_registers, 14, 60, 60, 33);
501	set_color_register(color_registers, 15, 80, 80, 80);
502	break;
503    case 382:			/* FIXME: verify */
504	set_color_register(color_registers, 0, 0, 0, 0);
505	set_color_register(color_registers, 1, 100, 100, 100);
506	break;
507    }
508
509#ifdef DEBUG_PALETTE
510    {
511	unsigned i;
512
513	for (i = 0U; i < MAX_COLOR_REGISTERS; i++) {
514	    printf("initial value for register %03u: %d,%d,%d\n",
515		   i,
516		   color_registers[i].r,
517		   color_registers[i].g,
518		   color_registers[i].b);
519	}
520    }
521#endif
522}
523
524unsigned
525get_color_register_count(TScreen const *screen)
526{
527    unsigned num_color_registers;
528
529    if (screen->numcolorregisters >= 0) {
530	num_color_registers = (unsigned) screen->numcolorregisters;
531    } else {
532	num_color_registers = 0U;
533    }
534
535    if (num_color_registers > 1U) {
536	if (num_color_registers > MAX_COLOR_REGISTERS)
537	    return MAX_COLOR_REGISTERS;
538	return num_color_registers;
539    }
540
541    /*
542     * color capabilities:
543     * VK100/GIGI  1 plane (12x1 pixel attribute blocks) colorspace is 8 fixed colors (black, white, red, green, blue, cyan, yellow, magenta)
544     * VT125       2 planes (4 registers) colorspace is (64?) (color), ? (grayscale)
545     * VT240       2 planes (4 registers) colorspace is 4 shades (grayscale)
546     * VT241       2 planes (4 registers) colorspace is ? (color), ? shades (grayscale)
547     * VT330       2 planes (4 registers) colorspace is 4 shades (grayscale)
548     * VT340       4 planes (16 registers) colorspace is r16g16b16 (color), 16 shades (grayscale)
549     * VT382       1 plane (two fixed colors: black and white)  FIXME: verify
550     * dxterm      ?
551     */
552    switch (screen->terminal_id) {
553    case 125:
554	return 4U;
555    case 240:
556	return 4U;
557    case 241:
558	return 4U;
559    case 330:
560	return 4U;
561    case 340:
562	return 16U;
563    case 382:
564	return 2U;
565    default:
566	/* unknown graphics model -- might as well be generous */
567	return MAX_COLOR_REGISTERS;
568    }
569}
570
571static void
572init_graphic(Graphic *graphic,
573	     unsigned type,
574	     int terminal_id,
575	     int charrow,
576	     int charcol,
577	     unsigned num_color_registers,
578	     int private_colors)
579{
580    unsigned i;
581
582    TRACE(("initializing graphic object\n"));
583
584    graphic->dirty = 1;
585    for (i = 0U; i < MAX_PIXELS; i++)
586	graphic->pixels[i] = COLOR_HOLE;
587    memset(graphic->color_registers_used, 0, sizeof(graphic->color_registers_used));
588
589    /*
590     * text and graphics interactions:
591     * VK100/GIGI                text writes on top of graphics buffer, color attribute shared with text
592     * VT240,VT241,VT330,VT340   text writes on top of graphics buffer
593     * VT382                     text writes on top of graphics buffer FIXME: verify
594     * VT125                     graphics buffer overlaid on top of text in B&W display, text not present in color display
595     */
596
597    /*
598     * dimensions (ReGIS logical, physical):
599     * VK100/GIGI  768x4??  768x240(status?)
600     * VT125       768x460  768x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
601     * VT240       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
602     * VT241       800x460  800x230(+10status) (1:2 aspect ratio, ReGIS halves vertical addresses through "odd y emulation")
603     * VT330       800x480  800x480(+?status)
604     * VT340       800x480  800x480(+?status)
605     * VT382       960x750  sixel only
606     * dxterm      ?x? ?x?  variable?
607     */
608    graphic->max_width = BUFFER_WIDTH;
609    graphic->max_height = BUFFER_HEIGHT;
610
611    graphic->actual_width = 0;
612    graphic->actual_height = 0;
613
614    graphic->pixw = 1;
615    graphic->pixh = 1;
616
617    graphic->valid_registers = num_color_registers;
618    TRACE(("%d color registers\n", graphic->valid_registers));
619
620    graphic->private_colors = private_colors;
621    if (graphic->private_colors) {
622	TRACE(("using private color registers\n"));
623	init_color_registers(graphic->private_color_registers, terminal_id);
624	graphic->color_registers = graphic->private_color_registers;
625    } else {
626	TRACE(("using shared color registers\n"));
627	graphic->color_registers = getSharedRegisters();
628    }
629
630    graphic->charrow = charrow;
631    graphic->charcol = charcol;
632    graphic->type = type;
633    graphic->valid = 0;
634}
635
636Graphic *
637get_new_graphic(XtermWidget xw, int charrow, int charcol, unsigned type)
638{
639    TScreen const *screen = TScreenOf(xw);
640    int bufferid = screen->whichBuf;
641    int terminal_id = screen->terminal_id;
642    Graphic *graphic;
643    unsigned ii;
644
645    FOR_EACH_SLOT(ii) {
646	if ((graphic = getInactiveSlot(ii))) {
647	    TRACE(("using fresh graphic index=%u id=%u\n", ii, next_graphic_id));
648	    break;
649	}
650    }
651
652    /* if none are free, recycle the graphic scrolled back the farthest */
653    if (!graphic) {
654	int min_charrow = 0;
655	Graphic *min_graphic = NULL;
656
657	FOR_EACH_SLOT(ii) {
658	    if (!(graphic = getActiveSlot(ii)))
659		continue;
660	    if (!min_graphic || graphic->charrow < min_charrow) {
661		min_charrow = graphic->charrow;
662		min_graphic = graphic;
663	    }
664	}
665	TRACE(("recycling old graphic index=%u id=%u\n", ii, next_graphic_id));
666	graphic = min_graphic;
667    }
668
669    if (graphic) {
670	unsigned num_color_registers;
671	num_color_registers = get_color_register_count(screen);
672	graphic->xw = xw;
673	graphic->bufferid = bufferid;
674	graphic->id = next_graphic_id++;
675	init_graphic(graphic,
676		     type,
677		     terminal_id,
678		     charrow,
679		     charcol,
680		     num_color_registers,
681		     screen->privatecolorregisters);
682    }
683    return graphic;
684}
685
686Graphic *
687get_new_or_matching_graphic(XtermWidget xw,
688			    int charrow,
689			    int charcol,
690			    int actual_width,
691			    int actual_height,
692			    unsigned type)
693{
694    TScreen const *screen = TScreenOf(xw);
695    int bufferid = screen->whichBuf;
696    Graphic *graphic;
697    unsigned ii;
698
699    FOR_EACH_SLOT(ii) {
700	if ((graphic = getActiveSlot(ii)) &&
701	    graphic->type == type &&
702	    graphic->bufferid == bufferid &&
703	    graphic->charrow == charrow &&
704	    graphic->charcol == charcol &&
705	    graphic->actual_width == actual_width &&
706	    graphic->actual_height == actual_height) {
707	    TRACE(("found existing graphic index=%u id=%u\n", ii, graphic->id));
708	    return graphic;
709	}
710    }
711
712    /* if no match get a new graphic */
713    if ((graphic = get_new_graphic(xw, charrow, charcol, type))) {
714	graphic->actual_width = actual_width;
715	graphic->actual_height = actual_height;
716    }
717    return graphic;
718}
719
720#define ScaleForXColor(s) (unsigned short) ((long)(s) * 65535 / 100)
721
722static Pixel
723color_register_to_xpixel(ColorRegister *reg, XtermWidget xw)
724{
725    if (!reg->allocated) {
726	XColor def;
727
728	def.red = ScaleForXColor(reg->r);
729	def.green = ScaleForXColor(reg->g);
730	def.blue = ScaleForXColor(reg->b);
731	def.flags = DoRed | DoGreen | DoBlue;
732	if (!allocateBestRGB(xw, &def)) {
733	    TRACE(("unable to allocate xcolor for color register\n"));
734	    return 0UL;
735	}
736	reg->pix = def.pixel;
737	reg->allocated = 1;
738    }
739
740    /* FIXME: with so many possible colors we need to determine
741     * when to free them to be nice to PseudoColor displays
742     */
743    return reg->pix;
744}
745
746static void
747refresh_graphic(TScreen const *screen,
748		Graphic const *graphic,
749		int xbase,
750		int ybase,
751		int x,
752		int y,
753		int w,
754		int h)
755{
756    Display *display = screen->display;
757    Window vwindow = WhichVWin(screen)->window;
758    GC graphics_gc;
759    int r, c;
760    int pw, ph;
761    int rbase, cbase;
762    RegisterNum color;
763    RegisterNum old_fg;
764    XGCValues xgcv;
765    XtGCMask mask;
766    int holes, total;
767
768    TRACE(("refreshing graphic from %d,%d %dx%d (valid=%d, size=%dx%d, scale=%dx%d max=%dx%d) at base=%d,%d\n",
769	   x, y, w, h,
770	   graphic->valid,
771	   graphic->actual_width,
772	   graphic->actual_height,
773	   graphic->pixw,
774	   graphic->pixh,
775	   graphic->max_width,
776	   graphic->max_height,
777	   xbase, ybase));
778
779    memset(&xgcv, 0, sizeof(xgcv));
780    xgcv.foreground = 0UL;
781    xgcv.graphics_exposures = False;
782    mask = GCForeground | GCGraphicsExposures;
783    graphics_gc = XCreateGC(display, vwindow, mask, &xgcv);
784
785    pw = graphic->pixw;
786    ph = graphic->pixh;
787
788    TRACE(("refreshed graphic covers 0,0 to %d,%d\n",
789	   (graphic->actual_width - 1) * pw + pw - 1,
790	   (graphic->actual_height - 1) * ph + ph - 1));
791    TRACE(("refreshed area covers %d,%d to %d,%d\n",
792	   x, y,
793	   x + w - 1,
794	   y + h - 1));
795
796    old_fg = COLOR_HOLE;
797    holes = total = 0;
798    rbase = 0;
799    for (r = 0; r < graphic->actual_height; r++) {
800	int rtest = rbase;
801
802	rbase += ph;
803	if (rtest + ph - 1 < y)
804	    continue;
805	if (rtest > y + h - 1)
806	    continue;
807
808	cbase = 0;
809	for (c = 0; c < graphic->actual_width; c++) {
810	    int ctest = cbase;
811
812	    cbase += pw;
813	    if (ctest + pw - 1 < x)
814		continue;
815	    if (ctest > x + w - 1)
816		continue;
817
818	    total++;
819	    color = graphic->pixels[r * graphic->max_width + c];
820	    if (color == COLOR_HOLE) {
821		holes++;
822		continue;
823	    }
824
825	    if (color != old_fg) {
826		xgcv.foreground =
827		    color_register_to_xpixel(&graphic->color_registers[color],
828					     graphic->xw);
829		XChangeGC(display, graphics_gc, mask, &xgcv);
830		old_fg = color;
831	    }
832
833	    XFillRectangle(display, vwindow, graphics_gc,
834			   xbase + ctest,
835			   ybase + rtest,
836			   (unsigned) pw,
837			   (unsigned) ph);
838	}
839    }
840
841#ifdef DEBUG_REFRESH
842    {
843	XColor def;
844
845	def.red = (short) (1.0 * 65535.0);
846	def.green = (short) (0.1 * 65535.0);
847	def.blue = (short) (1.0 * 65535.0);
848	def.flags = DoRed | DoGreen | DoBlue;
849	if (allocateBestRGB(graphic->xw, &def)) {
850	    xgcv.foreground = def.pixel;
851	    XChangeGC(display, graphics_gc, mask, &xgcv);
852	}
853	XFillRectangle(display, vwindow, graphics_gc,
854		       xbase + 0,
855		       ybase + 0,
856		       (unsigned) pw, (unsigned) ph);
857	XFillRectangle(display, vwindow, graphics_gc,
858		       xbase + (graphic->actual_width - 1) * pw,
859		       ybase + (graphic->actual_height - 1) * ph,
860		       (unsigned) pw, (unsigned) ph);
861
862	def.red = (unsigned short) ((1.0 - 0.1 * (rand() / (double)
863						  RAND_MAX) * 65535.0));
864	def.green = (unsigned short) ((0.7 + 0.2 * (rand() / (double)
865						    RAND_MAX)) * 65535.0);
866	def.blue = (unsigned short) ((0.1 + 0.1 * (rand() / (double)
867						   RAND_MAX)) * 65535.0);
868	def.flags = DoRed | DoGreen | DoBlue;
869	if (allocateBestRGB(graphic->xw, &def)) {
870	    xgcv.foreground = def.pixel;
871	    XChangeGC(display, graphics_gc, mask, &xgcv);
872	}
873	XDrawLine(display, vwindow, graphics_gc,
874		  xbase + x + 0, ybase + y + 0,
875		  xbase + x + w - 1, ybase + y + 0);
876	XDrawLine(display, vwindow, graphics_gc,
877		  xbase + x + w - 1, ybase + y + 0,
878		  xbase + x + 0, ybase + y + h - 1);
879	XDrawLine(display, vwindow, graphics_gc,
880		  xbase + x + 0, ybase + y + h - 1,
881		  xbase + x + w - 1, ybase + y + h - 1);
882	XDrawLine(display, vwindow, graphics_gc,
883		  xbase + x + w - 1, ybase + y + h - 1,
884		  xbase + x + 0, ybase + y + 0);
885    }
886#endif
887    XFlush(display);
888    TRACE(("done refreshing graphic: %d of %d refreshed pixels were holes\n",
889	   holes, total));
890
891    XFreeGC(display, graphics_gc);
892}
893
894/*
895 * Primary color hues:
896 *  blue:    0 degrees
897 *  red:   120 degrees
898 *  green: 240 degrees
899 */
900void
901hls2rgb(int h, int l, int s, short *r, short *g, short *b)
902{
903    double hs = (h + 240) % 360;
904    double hv = hs / 360.0;
905    double lv = l / 100.0;
906    double sv = s / 100.0;
907    double c, x, m, c2;
908    double r1, g1, b1;
909    int hpi;
910
911    if (s == 0) {
912	*r = *g = *b = (short) l;
913	return;
914    }
915
916    if ((c2 = ((2.0 * lv) - 1.0)) < 0.0)
917	c2 = -c2;
918    c = (1.0 - c2) * sv;
919    hpi = (int) (hv * 6.0);
920    x = (hpi & 1) ? c : 0.0;
921    m = lv - 0.5 * c;
922
923    switch (hpi) {
924    case 0:
925	r1 = c;
926	g1 = x;
927	b1 = 0.0;
928	break;
929    case 1:
930	r1 = x;
931	g1 = c;
932	b1 = 0.0;
933	break;
934    case 2:
935	r1 = 0.0;
936	g1 = c;
937	b1 = x;
938	break;
939    case 3:
940	r1 = 0.0;
941	g1 = x;
942	b1 = c;
943	break;
944    case 4:
945	r1 = x;
946	g1 = 0.0;
947	b1 = c;
948	break;
949    case 5:
950	r1 = c;
951	g1 = 0.0;
952	b1 = x;
953	break;
954    default:
955	TRACE(("Bad HLS input: [%d,%d,%d], returning white\n", h, l, s));
956	*r = (short) 100;
957	*g = (short) 100;
958	*b = (short) 100;
959	return;
960    }
961
962    *r = (short) ((r1 + m) * 100.0 + 0.5);
963    *g = (short) ((g1 + m) * 100.0 + 0.5);
964    *b = (short) ((b1 + m) * 100.0 + 0.5);
965
966    if (*r < 0)
967	*r = 0;
968    else if (*r > 100)
969	*r = 100;
970    if (*g < 0)
971	*g = 0;
972    else if (*g > 100)
973	*g = 100;
974    if (*b < 0)
975	*b = 0;
976    else if (*b > 100)
977	*b = 100;
978}
979
980void
981dump_graphic(Graphic const *graphic)
982{
983#if defined(DUMP_COLORS) || defined(DUMP_BITMAP)
984    RegisterNum color;
985#endif
986#ifdef DUMP_BITMAP
987    int r, c;
988    ColorRegister const *reg;
989#endif
990
991    (void) graphic;
992
993    TRACE(("graphic stats: id=%u charrow=%d charcol=%d actual_width=%d actual_height=%d pixw=%d pixh=%d\n",
994	   graphic->id,
995	   graphic->charrow,
996	   graphic->charcol,
997	   graphic->actual_width,
998	   graphic->actual_height,
999	   graphic->pixw,
1000	   graphic->pixh));
1001
1002#ifdef DUMP_COLORS
1003    TRACE(("graphic colors:\n"));
1004    for (color = 0; color < graphic->valid_registers; color++) {
1005	TRACE(("%03u: %d,%d,%d\n",
1006	       color,
1007	       graphic->color_registers[color].r,
1008	       graphic->color_registers[color].g,
1009	       graphic->color_registers[color].b));
1010    }
1011#endif
1012
1013#ifdef DUMP_BITMAP
1014    TRACE(("graphic pixels:\n"));
1015    for (r = 0; r < graphic->actual_height; r++) {
1016	for (c = 0; c < graphic->actual_width; c++) {
1017	    color = graphic->pixels[r * graphic->max_width + c];
1018	    if (color == COLOR_HOLE) {
1019		TRACE(("?"));
1020	    } else {
1021		reg = &graphic->color_registers[color];
1022		if (reg->r + reg->g + reg->b > 200) {
1023		    TRACE(("#"));
1024		} else if (reg->r + reg->g + reg->b > 150) {
1025		    TRACE(("%%"));
1026		} else if (reg->r + reg->g + reg->b > 100) {
1027		    TRACE((":"));
1028		} else if (reg->r + reg->g + reg->b > 80) {
1029		    TRACE(("."));
1030		} else {
1031		    TRACE((" "));
1032		}
1033	    }
1034	}
1035	TRACE(("\n"));
1036    }
1037
1038    TRACE(("\n"));
1039#endif
1040}
1041
1042/* Erase the portion of any displayed graphic overlapping with a rectangle
1043 * of the given size and location in pixels.
1044 * This is used to allow text to "erase" graphics underneath it.
1045 */
1046static void
1047erase_graphic(Graphic *graphic, int x, int y, int w, int h)
1048{
1049    RegisterNum hole = COLOR_HOLE;
1050    int pw, ph;
1051    int r, c;
1052    int rbase, cbase;
1053
1054    pw = graphic->pixw;
1055    ph = graphic->pixh;
1056
1057    TRACE(("erasing graphic %d,%d %dx%d\n", x, y, w, h));
1058
1059    rbase = 0;
1060    for (r = 0; r < graphic->actual_height; r++) {
1061	if (rbase + ph - 1 >= y
1062	    && rbase <= y + h - 1) {
1063	    cbase = 0;
1064	    for (c = 0; c < graphic->actual_width; c++) {
1065		if (cbase + pw - 1 >= x
1066		    && cbase <= x + w - 1) {
1067		    graphic->pixels[r * graphic->max_width + c] = hole;
1068		}
1069		cbase += pw;
1070	    }
1071	}
1072	rbase += ph;
1073    }
1074}
1075
1076static int
1077compare_graphic_ids(const void *left, const void *right)
1078{
1079    const Graphic *l = *(const Graphic *const *) left;
1080    const Graphic *r = *(const Graphic *const *) right;
1081
1082    if (!l->valid || !r->valid)
1083	return 0;
1084    if (l->id < r->id)
1085	return -1;
1086    else
1087	return 1;
1088}
1089
1090void
1091refresh_displayed_graphics(TScreen const *screen,
1092			   int leftcol,
1093			   int toprow,
1094			   int ncols,
1095			   int nrows)
1096{
1097    Graphic *ordered_graphics[MAX_GRAPHICS];
1098    Graphic *graphic;
1099    unsigned ii;
1100    unsigned jj = 0;
1101    int x, y, w, h;
1102    int xbase, ybase;
1103
1104    FOR_EACH_SLOT(ii) {
1105	if ((graphic = getActiveSlot(ii))) {
1106	    ordered_graphics[jj++] = graphic;
1107	}
1108    }
1109    if (jj > 1) {
1110	qsort(ordered_graphics,
1111	      (size_t) jj,
1112	      sizeof(ordered_graphics[0]),
1113	      compare_graphic_ids);
1114    }
1115
1116    for (ii = 0; ii < jj; ++ii) {
1117	graphic = ordered_graphics[ii];
1118	if (graphic->bufferid != screen->whichBuf)
1119	    continue;
1120
1121	x = (leftcol - graphic->charcol) * FontWidth(screen);
1122	y = (toprow - graphic->charrow) * FontHeight(screen);
1123	w = ncols * FontWidth(screen);
1124	h = nrows * FontHeight(screen);
1125
1126	xbase = (OriginX(screen)
1127		 + graphic->charcol * FontWidth(screen));
1128	ybase = (OriginY(screen)
1129		 + (graphic->charrow - screen->topline) * FontHeight(screen));
1130
1131	if (xbase + x + w + OriginX(screen) > FullWidth(screen))
1132	    w = FullWidth(screen) - (xbase + x + OriginX(screen));
1133	if (ybase + y + h + OriginY(screen) > FullHeight(screen))
1134	    h = FullHeight(screen) - (ybase + y + OriginY(screen));
1135	else if (ybase + y < OriginY(screen)) {
1136	    int diff = OriginY(screen) - (ybase + y);
1137	    y += diff;
1138	    h -= diff;
1139	}
1140
1141	TRACE(("graphics refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d xbase=%d ybase=%d\n",
1142	       screen->topline,
1143	       leftcol, toprow,
1144	       nrows, ncols,
1145	       x, y, w, h,
1146	       xbase, ybase));
1147	refresh_graphic(screen, graphic, xbase, ybase, x, y, w, h);
1148    }
1149}
1150
1151void
1152refresh_modified_displayed_graphics(TScreen const *screen)
1153{
1154    Graphic *graphic;
1155    unsigned ii;
1156    int leftcol, toprow;
1157    int nrows, ncols;
1158    int x, y, w, h;
1159    int xbase, ybase;
1160
1161    FOR_EACH_SLOT(ii) {
1162	if (!(graphic = getActiveSlot(ii)))
1163	    continue;
1164	if (graphic->bufferid != screen->whichBuf)
1165	    continue;
1166	if (!graphic->dirty)
1167	    continue;
1168
1169	leftcol = graphic->charcol;
1170	toprow = graphic->charrow;
1171	nrows = (((graphic->actual_height * graphic->pixh)
1172		  + FontHeight(screen) - 1)
1173		 / FontHeight(screen));
1174	ncols = (((graphic->actual_width * graphic->pixw)
1175		  + FontWidth(screen) - 1)
1176		 / FontWidth(screen));
1177
1178	x = (leftcol - graphic->charcol) * FontWidth(screen);
1179	y = (toprow - graphic->charrow) * FontHeight(screen);
1180	w = ncols * FontWidth(screen);
1181	h = nrows * FontHeight(screen);
1182
1183	xbase = (OriginX(screen)
1184		 + graphic->charcol * FontWidth(screen));
1185	ybase = (OriginY(screen)
1186		 + (graphic->charrow - screen->topline) * FontHeight(screen));
1187
1188	if (xbase + x + w + OriginX(screen) > FullWidth(screen))
1189	    w = FullWidth(screen) - (xbase + x + OriginX(screen));
1190	if (ybase + y + h + OriginY(screen) > FullHeight(screen))
1191	    h = FullHeight(screen) - (ybase + y + OriginY(screen));
1192	else if (ybase + y < OriginY(screen)) {
1193	    int diff = OriginY(screen) - (ybase + y);
1194	    y += diff;
1195	    h -= diff;
1196	}
1197
1198	TRACE(("full graphics refresh: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d xbase=%d ybase=%d\n",
1199	       screen->topline,
1200	       leftcol, toprow,
1201	       nrows, ncols,
1202	       x, y, w, h,
1203	       xbase, ybase));
1204	refresh_graphic(screen, graphic, xbase, ybase, x, y, w, h);
1205	graphic->dirty = 0;
1206    }
1207}
1208
1209void
1210scroll_displayed_graphics(int rows)
1211{
1212    Graphic *graphic;
1213    unsigned ii;
1214
1215    TRACE(("graphics scroll: moving all up %d rows\n", rows));
1216    /* FIXME: VT125 ReGIS graphics are fixed at the upper left of the display; need to verify */
1217
1218    FOR_EACH_SLOT(ii) {
1219	if (!(graphic = getActiveSlot(ii)))
1220	    continue;
1221
1222	graphic->charrow -= rows;
1223    }
1224}
1225
1226void
1227pixelarea_clear_displayed_graphics(TScreen const *screen,
1228				   int winx,
1229				   int winy,
1230				   int w,
1231				   int h)
1232{
1233    Graphic *graphic;
1234    unsigned ii;
1235    int x, y;
1236
1237    FOR_EACH_SLOT(ii) {
1238	if (!(graphic = getActiveSlot(ii)))
1239	    continue;
1240
1241	x = winx - graphic->charcol * FontWidth(screen);
1242	y = winy - graphic->charrow * FontHeight(screen);
1243
1244	TRACE(("pixelarea graphics erase: screen->topline=%d winx=%d winy=%d w=%d h=%d x=%d y=%d\n",
1245	       screen->topline,
1246	       winx, winy,
1247	       w, h,
1248	       x, y));
1249	erase_graphic(graphic, x, y, w, h);
1250    }
1251}
1252
1253void
1254chararea_clear_displayed_graphics(TScreen const *screen,
1255				  int leftcol,
1256				  int toprow,
1257				  int ncols,
1258				  int nrows)
1259{
1260    int x, y, w, h;
1261
1262    x = leftcol * FontWidth(screen);
1263    y = toprow * FontHeight(screen);
1264    w = ncols * FontWidth(screen);
1265    h = nrows * FontHeight(screen);
1266
1267    TRACE(("chararea clear graphics: screen->topline=%d leftcol=%d toprow=%d nrows=%d ncols=%d x=%d y=%d w=%d h=%d\n",
1268	   screen->topline,
1269	   leftcol, toprow,
1270	   nrows, ncols,
1271	   x, y, w, h));
1272    pixelarea_clear_displayed_graphics(screen, x, y, w, h);
1273}
1274
1275void
1276reset_displayed_graphics(TScreen const *screen)
1277{
1278    unsigned ii;
1279
1280    init_color_registers(getSharedRegisters(), screen->terminal_id);
1281
1282    TRACE(("resetting all graphics\n"));
1283    FOR_EACH_SLOT(ii) {
1284	deactivateSlot(ii);
1285    }
1286}
1287
1288#ifdef NO_LEAKS
1289void
1290noleaks_graphics(void)
1291{
1292    unsigned ii;
1293
1294    FOR_EACH_SLOT(ii) {
1295	deactivateSlot(ii);
1296    }
1297}
1298#endif
1299