1 | /* analogtv, Copyright (c) 2003 Trevor Blackwell <tlb@tlb.org> |
---|
2 | * |
---|
3 | * Permission to use, copy, modify, distribute, and sell this software and its |
---|
4 | * documentation for any purpose is hereby granted without fee, provided that |
---|
5 | * the above copyright notice appear in all copies and that both that |
---|
6 | * copyright notice and this permission notice appear in supporting |
---|
7 | * documentation. No representations are made about the suitability of this |
---|
8 | * software for any purpose. It is provided "as is" without express or |
---|
9 | * implied warranty. |
---|
10 | */ |
---|
11 | |
---|
12 | /* |
---|
13 | |
---|
14 | This is the code for implementing something that looks like a conventional |
---|
15 | analog TV set. It simulates the following characteristics of standard |
---|
16 | televisions: |
---|
17 | |
---|
18 | - Realistic rendering of a composite video signal |
---|
19 | - Compression & brightening on the right, as the scan gets truncated |
---|
20 | because of saturation in the flyback transformer |
---|
21 | - Blooming of the picture dependent on brightness |
---|
22 | - Overscan, cutting off a few pixels on the left side. |
---|
23 | - Colored text in mixed graphics/text modes |
---|
24 | |
---|
25 | It's amazing how much it makes your high-end monitor look like at large |
---|
26 | late-70s TV. All you need is to put a big "Solid State" logo in curly script |
---|
27 | on it and you'd be set. |
---|
28 | |
---|
29 | In DirectColor or TrueColor modes, it generates pixel values |
---|
30 | directly from RGB values it calculates across each scan line. In |
---|
31 | PseudoColor mode, it consider each possible pattern of 5 preceding |
---|
32 | bit values in each possible position modulo 4 and allocates a color |
---|
33 | for each. A few things, like the brightening on the right side as |
---|
34 | the horizontal trace slows down, aren't done in PseudoColor. |
---|
35 | |
---|
36 | I originally wrote it for the Apple ][ emulator, and generalized it |
---|
37 | here for use with a rewrite of xteevee and possibly others. |
---|
38 | |
---|
39 | A maxim of technology is that failures reveal underlying mechanism. |
---|
40 | A good way to learn how something works is to push it to failure. |
---|
41 | The way it fails will usually tell you a lot about how it works. The |
---|
42 | corollary for this piece of software is that in order to emulate |
---|
43 | realistic failures of a TV set, it has to work just like a TV set. |
---|
44 | So there is lots of DSP-style emulation of analog circuitry for |
---|
45 | things like color decoding, H and V sync following, and more. In |
---|
46 | 2003, computers are just fast enough to do this at television signal |
---|
47 | rates. We use a 14 MHz sample rate here, so we can do on the order |
---|
48 | of a couple hundred instructions per sample and keep a good frame |
---|
49 | rate. |
---|
50 | |
---|
51 | Trevor Blackwell <tlb@tlb.org> |
---|
52 | */ |
---|
53 | |
---|
54 | #include <X11/Xlib.h> |
---|
55 | #include <X11/Xutil.h> |
---|
56 | #include <X11/Intrinsic.h> |
---|
57 | #include <assert.h> |
---|
58 | #include "utils.h" |
---|
59 | #include "resources.h" |
---|
60 | #include "analogtv.h" |
---|
61 | #include "yarandom.h" |
---|
62 | #include "grabscreen.h" |
---|
63 | |
---|
64 | /* #define DEBUG 1 */ |
---|
65 | |
---|
66 | #ifdef DEBUG |
---|
67 | /* only works on linux + freebsd */ |
---|
68 | #include <machine/cpufunc.h> |
---|
69 | |
---|
70 | #define DTIME_DECL u_int64_t dtimes[100]; int n_dtimes |
---|
71 | #define DTIME_START do {n_dtimes=0; dtimes[n_dtimes++]=rdtsc(); } while (0) |
---|
72 | #define DTIME dtimes[n_dtimes++]=rdtsc() |
---|
73 | #define DTIME_SHOW(DIV) \ |
---|
74 | do { \ |
---|
75 | double _dtime_div=(DIV); \ |
---|
76 | printf("time/%.1f: ",_dtime_div); \ |
---|
77 | for (i=1; i<n_dtimes; i++) \ |
---|
78 | printf(" %0.9f",(dtimes[i]-dtimes[i-1])* 1e-9 / _dtime_div); \ |
---|
79 | printf("\n"); \ |
---|
80 | } while (0) |
---|
81 | |
---|
82 | #else |
---|
83 | |
---|
84 | #define DTIME_DECL |
---|
85 | #define DTIME_START do { } while (0) |
---|
86 | #define DTIME do { } while (0) |
---|
87 | #define DTIME_SHOW(DIV) do { } while (0) |
---|
88 | |
---|
89 | #endif |
---|
90 | |
---|
91 | |
---|
92 | #define FASTRND (fastrnd = fastrnd*1103515245+12345) |
---|
93 | |
---|
94 | static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, |
---|
95 | int start, int end); |
---|
96 | |
---|
97 | static double puramp(analogtv *it, double tc, double start, double over) |
---|
98 | { |
---|
99 | double pt=it->powerup-start; |
---|
100 | double ret; |
---|
101 | if (pt<0.0) return 0.0; |
---|
102 | if (pt>900.0 || pt/tc>8.0) return 1.0; |
---|
103 | |
---|
104 | ret=(1.0-exp(-pt/tc))*over; |
---|
105 | if (ret>1.0) return 1.0; |
---|
106 | return ret*ret; |
---|
107 | } |
---|
108 | |
---|
109 | /* |
---|
110 | There are actual standards for TV signals: NTSC and RS-170A describe the |
---|
111 | system used in the US and Japan. Europe has slightly different systems, but |
---|
112 | not different enough to make substantially different screensaver displays. |
---|
113 | Sadly, the standards bodies don't do anything so useful as publish the spec on |
---|
114 | the web. Best bets are: |
---|
115 | |
---|
116 | http://www.ee.washington.edu/conselec/CE/kuhn/ntsc/95x4.htm |
---|
117 | http://www.ntsc-tv.com/ntsc-index-02.htm |
---|
118 | |
---|
119 | In DirectColor or TrueColor modes, it generates pixel values directly from RGB |
---|
120 | values it calculates across each scan line. In PseudoColor mode, it consider |
---|
121 | each possible pattern of 5 preceding bit values in each possible position |
---|
122 | modulo 4 and allocates a color for each. A few things, like the brightening on |
---|
123 | the right side as the horizontal trace slows down, aren't done in PseudoColor. |
---|
124 | |
---|
125 | I'd like to add a bit of visible retrace, but it conflicts with being able to |
---|
126 | bitcopy the image when fast scrolling. After another couple of CPU |
---|
127 | generations, we could probably regenerate the whole image from scratch every |
---|
128 | time. On a P4 2 GHz it can manage this fine for blinking text, but scrolling |
---|
129 | looks too slow. |
---|
130 | */ |
---|
131 | |
---|
132 | /* localbyteorder is MSBFirst or LSBFirst */ |
---|
133 | static int localbyteorder; |
---|
134 | static const double float_low8_ofs=8388608.0; |
---|
135 | static int float_extraction_works; |
---|
136 | |
---|
137 | typedef union { |
---|
138 | float f; |
---|
139 | int i; |
---|
140 | } float_extract_t; |
---|
141 | |
---|
142 | static void |
---|
143 | analogtv_init(void) |
---|
144 | { |
---|
145 | int i; |
---|
146 | { |
---|
147 | unsigned int localbyteorder_loc = (MSBFirst<<24) | (LSBFirst<<0); |
---|
148 | localbyteorder=*(char *)&localbyteorder_loc; |
---|
149 | } |
---|
150 | |
---|
151 | if (1) { |
---|
152 | float_extract_t fe; |
---|
153 | int ans; |
---|
154 | |
---|
155 | float_extraction_works=1; |
---|
156 | for (i=0; i<256*4; i++) { |
---|
157 | fe.f=float_low8_ofs+(double)i; |
---|
158 | ans=fe.i&0x3ff; |
---|
159 | if (ans != i) { |
---|
160 | #ifdef DEBUG |
---|
161 | printf("Float extraction failed for %d => %d\n",i,ans); |
---|
162 | #endif |
---|
163 | float_extraction_works=0; |
---|
164 | break; |
---|
165 | } |
---|
166 | } |
---|
167 | } |
---|
168 | |
---|
169 | } |
---|
170 | |
---|
171 | void |
---|
172 | analogtv_set_defaults(analogtv *it, char *prefix) |
---|
173 | { |
---|
174 | char buf[256]; |
---|
175 | |
---|
176 | sprintf(buf,"%sTVTint",prefix); |
---|
177 | it->tint_control = get_float_resource(buf,"TVTint"); |
---|
178 | sprintf(buf,"%sTVColor",prefix); |
---|
179 | it->color_control = get_float_resource(buf,"TVColor")/100.0; |
---|
180 | sprintf(buf,"%sTVBrightness",prefix); |
---|
181 | it->brightness_control = get_float_resource(buf,"TVBrightness") / 100.0; |
---|
182 | sprintf(buf,"%sTVContrast",prefix); |
---|
183 | it->contrast_control = get_float_resource(buf,"TVContrast") / 100.0; |
---|
184 | it->height_control = 1.0; |
---|
185 | it->width_control = 1.0; |
---|
186 | it->squish_control = 0.0; |
---|
187 | it->powerup=1000.0; |
---|
188 | |
---|
189 | it->hashnoise_rpm=0; |
---|
190 | it->hashnoise_on=0; |
---|
191 | it->hashnoise_enable=1; |
---|
192 | |
---|
193 | it->horiz_desync=frand(10.0)-5.0; |
---|
194 | it->squeezebottom=frand(5.0)-1.0; |
---|
195 | |
---|
196 | #ifdef DEBUG |
---|
197 | printf("analogtv: prefix=%s\n",prefix); |
---|
198 | printf(" use: shm=%d cmap=%d color=%d\n", |
---|
199 | it->use_shm,it->use_cmap,it->use_color); |
---|
200 | printf(" controls: tint=%g color=%g brightness=%g contrast=%g\n", |
---|
201 | it->tint_control, it->color_control, it->brightness_control, |
---|
202 | it->contrast_control); |
---|
203 | printf(" freq_error %g: %g %d\n", |
---|
204 | it->freq_error, it->freq_error_inc, it->flutter_tint); |
---|
205 | printf(" desync: %g %d\n", |
---|
206 | it->horiz_desync, it->flutter_horiz_desync); |
---|
207 | printf(" hashnoise rpm: %g\n", |
---|
208 | it->hashnoise_rpm); |
---|
209 | printf(" vis: %d %d %d\n", |
---|
210 | it->visclass, it->visbits, it->visdepth); |
---|
211 | printf(" shift: %d-%d %d-%d %d-%d\n", |
---|
212 | it->red_invprec,it->red_shift, |
---|
213 | it->green_invprec,it->green_shift, |
---|
214 | it->blue_invprec,it->blue_shift); |
---|
215 | printf(" size: %d %d %d %d xrepl=%d\n", |
---|
216 | it->usewidth, it->useheight, |
---|
217 | it->screen_xo, it->screen_yo, it->xrepl); |
---|
218 | |
---|
219 | printf(" ANALOGTV_V=%d\n",ANALOGTV_V); |
---|
220 | printf(" ANALOGTV_TOP=%d\n",ANALOGTV_TOP); |
---|
221 | printf(" ANALOGTV_VISLINES=%d\n",ANALOGTV_VISLINES); |
---|
222 | printf(" ANALOGTV_BOT=%d\n",ANALOGTV_BOT); |
---|
223 | printf(" ANALOGTV_H=%d\n",ANALOGTV_H); |
---|
224 | printf(" ANALOGTV_SYNC_START=%d\n",ANALOGTV_SYNC_START); |
---|
225 | printf(" ANALOGTV_BP_START=%d\n",ANALOGTV_BP_START); |
---|
226 | printf(" ANALOGTV_CB_START=%d\n",ANALOGTV_CB_START); |
---|
227 | printf(" ANALOGTV_PIC_START=%d\n",ANALOGTV_PIC_START); |
---|
228 | printf(" ANALOGTV_PIC_LEN=%d\n",ANALOGTV_PIC_LEN); |
---|
229 | printf(" ANALOGTV_FP_START=%d\n",ANALOGTV_FP_START); |
---|
230 | printf(" ANALOGTV_PIC_END=%d\n",ANALOGTV_PIC_END); |
---|
231 | printf(" ANALOGTV_HASHNOISE_LEN=%d\n",ANALOGTV_HASHNOISE_LEN); |
---|
232 | |
---|
233 | #endif |
---|
234 | |
---|
235 | } |
---|
236 | |
---|
237 | extern Bool mono_p; /* shoot me */ |
---|
238 | |
---|
239 | void |
---|
240 | analogtv_free_image(analogtv *it) |
---|
241 | { |
---|
242 | if (it->image) { |
---|
243 | if (it->use_shm) { |
---|
244 | #ifdef HAVE_XSHM_EXTENSION |
---|
245 | destroy_xshm_image(it->dpy, it->image, &it->shm_info); |
---|
246 | #endif |
---|
247 | } else { |
---|
248 | XDestroyImage(it->image); |
---|
249 | } |
---|
250 | it->image=NULL; |
---|
251 | } |
---|
252 | } |
---|
253 | |
---|
254 | void |
---|
255 | analogtv_alloc_image(analogtv *it) |
---|
256 | { |
---|
257 | if (it->use_shm) { |
---|
258 | #ifdef HAVE_XSHM_EXTENSION |
---|
259 | it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, |
---|
260 | &it->shm_info, it->usewidth, it->useheight); |
---|
261 | #endif |
---|
262 | if (!it->image) it->use_shm=0; |
---|
263 | } |
---|
264 | if (!it->image) { |
---|
265 | it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0, |
---|
266 | it->usewidth, it->useheight, 8, 0); |
---|
267 | it->image->data = (char *)calloc(it->image->height, it->image->bytes_per_line); |
---|
268 | } |
---|
269 | } |
---|
270 | |
---|
271 | |
---|
272 | void |
---|
273 | analogtv_configure(analogtv *it) |
---|
274 | { |
---|
275 | int oldwidth=it->usewidth; |
---|
276 | int oldheight=it->useheight; |
---|
277 | int wlim,hlim,ohlim; |
---|
278 | |
---|
279 | hlim=it->xgwa.height; |
---|
280 | if (hlim<ANALOGTV_VISLINES) hlim = ANALOGTV_VISLINES; |
---|
281 | |
---|
282 | wlim = it->xgwa.width; |
---|
283 | if (wlim<300) wlim = 300; |
---|
284 | |
---|
285 | /* require 3:4 aspect ratio */ |
---|
286 | if (wlim > hlim*4/3) wlim=hlim*4/3; |
---|
287 | if (hlim > wlim*3/4) hlim=wlim*3/4; |
---|
288 | |
---|
289 | /* height must be a multiple of VISLINES */ |
---|
290 | ohlim=hlim; |
---|
291 | hlim = (hlim/ANALOGTV_VISLINES)*ANALOGTV_VISLINES; |
---|
292 | |
---|
293 | /* Scale width proportionally */ |
---|
294 | wlim=wlim*hlim/ohlim; |
---|
295 | |
---|
296 | { |
---|
297 | FILE *fp=fopen("/tmp/analogtv.size","w"); |
---|
298 | fprintf(fp,"wlim=%d hlim=%d\n", wlim, hlim); |
---|
299 | fclose(fp); |
---|
300 | } |
---|
301 | |
---|
302 | /* Most times this doesn't change */ |
---|
303 | if (wlim != oldwidth || hlim != oldheight) { |
---|
304 | |
---|
305 | it->usewidth=wlim; |
---|
306 | it->useheight=hlim; |
---|
307 | |
---|
308 | it->xrepl=1+it->usewidth/640; |
---|
309 | if (it->xrepl>2) it->xrepl=2; |
---|
310 | it->subwidth=it->usewidth/it->xrepl; |
---|
311 | |
---|
312 | analogtv_free_image(it); |
---|
313 | analogtv_alloc_image(it); |
---|
314 | } |
---|
315 | |
---|
316 | it->screen_xo = (it->xgwa.width-it->usewidth)/2; |
---|
317 | it->screen_yo = (it->xgwa.height-it->useheight)/2; |
---|
318 | it->need_clear=1; |
---|
319 | } |
---|
320 | |
---|
321 | void |
---|
322 | analogtv_reconfigure(analogtv *it) |
---|
323 | { |
---|
324 | XGetWindowAttributes (it->dpy, it->window, &it->xgwa); |
---|
325 | analogtv_configure(it); |
---|
326 | } |
---|
327 | |
---|
328 | analogtv * |
---|
329 | analogtv_allocate(Display *dpy, Window window) |
---|
330 | { |
---|
331 | XGCValues gcv; |
---|
332 | analogtv *it=NULL; |
---|
333 | int i; |
---|
334 | |
---|
335 | analogtv_init(); |
---|
336 | |
---|
337 | it=(analogtv *)calloc(1,sizeof(analogtv)); |
---|
338 | it->dpy=dpy; |
---|
339 | it->window=window; |
---|
340 | |
---|
341 | it->shrinkpulse=-1; |
---|
342 | |
---|
343 | it->n_colors=0; |
---|
344 | |
---|
345 | #ifdef HAVE_XSHM_EXTENSION |
---|
346 | it->use_shm=1; |
---|
347 | #else |
---|
348 | it->use_shm=0; |
---|
349 | #endif |
---|
350 | |
---|
351 | XGetWindowAttributes (it->dpy, it->window, &it->xgwa); |
---|
352 | |
---|
353 | it->screen=it->xgwa.screen; |
---|
354 | it->colormap=it->xgwa.colormap; |
---|
355 | it->visclass=it->xgwa.visual->class; |
---|
356 | it->visbits=it->xgwa.visual->bits_per_rgb; |
---|
357 | it->visdepth=it->xgwa.depth; |
---|
358 | if (it->visclass == TrueColor || it->visclass == DirectColor) { |
---|
359 | if (get_integer_resource ("use_cmap", "Integer")) { |
---|
360 | it->use_cmap=1; |
---|
361 | } else { |
---|
362 | it->use_cmap=0; |
---|
363 | } |
---|
364 | it->use_color=!mono_p; |
---|
365 | } |
---|
366 | else if (it->visclass == PseudoColor || it->visclass == StaticColor) { |
---|
367 | it->use_cmap=1; |
---|
368 | it->use_color=!mono_p; |
---|
369 | } |
---|
370 | else { |
---|
371 | it->use_cmap=1; |
---|
372 | it->use_color=0; |
---|
373 | } |
---|
374 | |
---|
375 | it->red_mask=it->xgwa.visual->red_mask; |
---|
376 | it->green_mask=it->xgwa.visual->green_mask; |
---|
377 | it->blue_mask=it->xgwa.visual->blue_mask; |
---|
378 | it->red_shift=it->red_invprec=-1; |
---|
379 | it->green_shift=it->green_invprec=-1; |
---|
380 | it->blue_shift=it->blue_invprec=-1; |
---|
381 | if (!it->use_cmap) { |
---|
382 | /* Is there a standard way to do this? Does this handle all cases? */ |
---|
383 | int shift, prec; |
---|
384 | for (shift=0; shift<32; shift++) { |
---|
385 | for (prec=1; prec<16 && prec<32-shift; prec++) { |
---|
386 | unsigned long mask=(0xffffUL>>(16-prec)) << shift; |
---|
387 | if (it->red_shift<0 && mask==it->red_mask) |
---|
388 | it->red_shift=shift, it->red_invprec=16-prec; |
---|
389 | if (it->green_shift<0 && mask==it->green_mask) |
---|
390 | it->green_shift=shift, it->green_invprec=16-prec; |
---|
391 | if (it->blue_shift<0 && mask==it->blue_mask) |
---|
392 | it->blue_shift=shift, it->blue_invprec=16-prec; |
---|
393 | } |
---|
394 | } |
---|
395 | if (it->red_shift<0 || it->green_shift<0 || it->blue_shift<0) { |
---|
396 | if (0) fprintf(stderr,"Can't figure out color space\n"); |
---|
397 | goto fail; |
---|
398 | } |
---|
399 | |
---|
400 | for (i=0; i<ANALOGTV_CV_MAX; i++) { |
---|
401 | int intensity=pow(i/256.0, 0.8)*65535.0; /* gamma correction */ |
---|
402 | if (intensity>65535) intensity=65535; |
---|
403 | it->red_values[i]=((intensity>>it->red_invprec)<<it->red_shift); |
---|
404 | it->green_values[i]=((intensity>>it->green_invprec)<<it->green_shift); |
---|
405 | it->blue_values[i]=((intensity>>it->blue_invprec)<<it->blue_shift); |
---|
406 | } |
---|
407 | |
---|
408 | } |
---|
409 | |
---|
410 | gcv.background=get_pixel_resource("background", "Background", |
---|
411 | it->dpy, it->colormap); |
---|
412 | |
---|
413 | it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv); |
---|
414 | XSetWindowBackground(it->dpy, it->window, gcv.background); |
---|
415 | XClearWindow(dpy,window); |
---|
416 | |
---|
417 | analogtv_configure(it); |
---|
418 | |
---|
419 | return it; |
---|
420 | |
---|
421 | fail: |
---|
422 | if (it) free(it); |
---|
423 | return NULL; |
---|
424 | } |
---|
425 | |
---|
426 | void |
---|
427 | analogtv_release(analogtv *it) |
---|
428 | { |
---|
429 | if (it->image) { |
---|
430 | if (it->use_shm) { |
---|
431 | #ifdef HAVE_XSHM_EXTENSION |
---|
432 | destroy_xshm_image(it->dpy, it->image, &it->shm_info); |
---|
433 | #endif |
---|
434 | } else { |
---|
435 | XDestroyImage(it->image); |
---|
436 | } |
---|
437 | it->image=NULL; |
---|
438 | } |
---|
439 | if (it->gc) XFreeGC(it->dpy, it->gc); |
---|
440 | it->gc=NULL; |
---|
441 | if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L); |
---|
442 | it->n_colors=0; |
---|
443 | } |
---|
444 | |
---|
445 | |
---|
446 | /* |
---|
447 | First generate the I and Q reference signals, which we'll multiply |
---|
448 | the input signal by to accomplish the demodulation. Normally they |
---|
449 | are shifted 33 degrees from the colorburst. I think this was convenient |
---|
450 | for inductor-capacitor-vacuum tube implementation. |
---|
451 | |
---|
452 | The tint control, FWIW, just adds a phase shift to the chroma signal, |
---|
453 | and the color control controls the amplitude. |
---|
454 | |
---|
455 | In text modes (colormode==0) the system disabled the color burst, and no |
---|
456 | color was detected by the monitor. |
---|
457 | |
---|
458 | freq_error gives a mismatch between the built-in oscillator and the |
---|
459 | TV's colorbust. Some II Plus machines seemed to occasionally get |
---|
460 | instability problems -- the crystal oscillator was a single |
---|
461 | transistor if I remember correctly -- and the frequency would vary |
---|
462 | enough that the tint would change across the width of the screen. |
---|
463 | The left side would be in correct tint because it had just gotten |
---|
464 | resynchronized with the color burst. |
---|
465 | |
---|
466 | If we're using a colormap, set it up. |
---|
467 | */ |
---|
468 | int |
---|
469 | analogtv_set_demod(analogtv *it) |
---|
470 | { |
---|
471 | int y_levels=10,i_levels=5,q_levels=5; |
---|
472 | |
---|
473 | /* |
---|
474 | In principle, we might be able to figure out how to adjust the |
---|
475 | color map frame-by-frame to get some nice color bummage. But I'm |
---|
476 | terrified of changing the color map because we'll get flashing. |
---|
477 | |
---|
478 | I can hardly believe we still have to deal with colormaps. They're |
---|
479 | like having NEAR PTRs: an enormous hassle for the programmer just |
---|
480 | to save on memory. They should have been deprecated by 1995 or |
---|
481 | so. */ |
---|
482 | |
---|
483 | cmap_again: |
---|
484 | if (it->use_cmap && !it->n_colors) { |
---|
485 | |
---|
486 | if (it->n_colors) { |
---|
487 | XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L); |
---|
488 | it->n_colors=0; |
---|
489 | } |
---|
490 | |
---|
491 | { |
---|
492 | int yli,qli,ili; |
---|
493 | for (yli=0; yli<y_levels; yli++) { |
---|
494 | for (ili=0; ili<i_levels; ili++) { |
---|
495 | for (qli=0; qli<q_levels; qli++) { |
---|
496 | double interpy,interpi,interpq; |
---|
497 | double levelmult=700.0; |
---|
498 | int r,g,b; |
---|
499 | XColor col; |
---|
500 | |
---|
501 | interpy=100.0 * ((double)yli/y_levels); |
---|
502 | interpi=50.0 * (((double)ili-(0.5*i_levels))/(double)i_levels); |
---|
503 | interpq=50.0 * (((double)qli-(0.5*q_levels))/(double)q_levels); |
---|
504 | |
---|
505 | r=(int)((interpy + 1.04*interpi + 0.624*interpq)*levelmult); |
---|
506 | g=(int)((interpy - 0.276*interpi - 0.639*interpq)*levelmult); |
---|
507 | b=(int)((interpy - 1.105*interpi + 1.729*interpq)*levelmult); |
---|
508 | if (r<0) r=0; |
---|
509 | if (r>65535) r=65535; |
---|
510 | if (g<0) g=0; |
---|
511 | if (g>65535) g=65535; |
---|
512 | if (b<0) b=0; |
---|
513 | if (b>65535) b=65535; |
---|
514 | |
---|
515 | #ifdef DEBUG |
---|
516 | printf("%0.2f %0.2f %0.2f => %02x%02x%02x\n", |
---|
517 | interpy, interpi, interpq, |
---|
518 | r/256,g/256,b/256); |
---|
519 | #endif |
---|
520 | |
---|
521 | col.red=r; |
---|
522 | col.green=g; |
---|
523 | col.blue=b; |
---|
524 | col.pixel=0; |
---|
525 | if (!XAllocColor(it->dpy, it->colormap, &col)) { |
---|
526 | if (q_levels > y_levels*4/12) |
---|
527 | q_levels--; |
---|
528 | else if (i_levels > y_levels*5/12) |
---|
529 | i_levels--; |
---|
530 | else |
---|
531 | y_levels--; |
---|
532 | |
---|
533 | if (y_levels<2) |
---|
534 | return -1; |
---|
535 | goto cmap_again; |
---|
536 | } |
---|
537 | it->colors[it->n_colors++]=col.pixel; |
---|
538 | } |
---|
539 | } |
---|
540 | } |
---|
541 | |
---|
542 | it->cmap_y_levels=y_levels; |
---|
543 | it->cmap_i_levels=i_levels; |
---|
544 | it->cmap_q_levels=q_levels; |
---|
545 | } |
---|
546 | } |
---|
547 | |
---|
548 | return 0; |
---|
549 | } |
---|
550 | |
---|
551 | #if 0 |
---|
552 | unsigned int |
---|
553 | analogtv_line_signature(analogtv_input *input, int lineno) |
---|
554 | { |
---|
555 | int i; |
---|
556 | char *origsignal=&input->signal[(lineno+input->vsync) |
---|
557 | %ANALOGTV_V][input->line_hsync[lineno]]; |
---|
558 | unsigned int hash=0; |
---|
559 | |
---|
560 | /* probably lame */ |
---|
561 | for (i=0; i<ANALOGTV_PIC_LEN; i++) { |
---|
562 | int c=origsignal[i]; |
---|
563 | hash = hash + (hash<<17) + c; |
---|
564 | } |
---|
565 | |
---|
566 | hash += input->line_hsync[lineno]; |
---|
567 | hash ^= hash >> 2; |
---|
568 | /* |
---|
569 | hash += input->hashnoise_times[lineno]; |
---|
570 | hash ^= hash >> 2; |
---|
571 | */ |
---|
572 | |
---|
573 | return hash; |
---|
574 | } |
---|
575 | #endif |
---|
576 | |
---|
577 | |
---|
578 | /* Here we model the analog circuitry of an NTSC television. |
---|
579 | Basically, it splits the signal into 3 signals: Y, I and Q. Y |
---|
580 | corresponds to luminance, and you get it by low-pass filtering the |
---|
581 | input signal to below 3.57 MHz. |
---|
582 | |
---|
583 | I and Q are the in-phase and quadrature components of the 3.57 MHz |
---|
584 | subcarrier. We get them by multiplying by cos(3.57 MHz*t) and |
---|
585 | sin(3.57 MHz*t), and low-pass filtering. Because the eye has less |
---|
586 | resolution in some colors than others, the I component gets |
---|
587 | low-pass filtered at 1.5 MHz and the Q at 0.5 MHz. The I component |
---|
588 | is approximately orange-blue, and Q is roughly purple-green. See |
---|
589 | http://www.ntsc-tv.com for details. |
---|
590 | |
---|
591 | We actually do an awful lot to the signal here. I suspect it would |
---|
592 | make sense to wrap them all up together by calculating impulse |
---|
593 | response and doing FFT convolutions. |
---|
594 | |
---|
595 | */ |
---|
596 | |
---|
597 | static void |
---|
598 | analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, |
---|
599 | int start, int end) |
---|
600 | { |
---|
601 | enum {MAXDELAY=32}; |
---|
602 | int i; |
---|
603 | double *sp; |
---|
604 | int phasecorr=(signal-it->rx_signal)&3; |
---|
605 | struct analogtv_yiq_s *yiq; |
---|
606 | int colormode; |
---|
607 | double agclevel=it->agclevel; |
---|
608 | double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL; |
---|
609 | double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp; |
---|
610 | double multiq2[4]; |
---|
611 | |
---|
612 | { |
---|
613 | |
---|
614 | double cb_i=(it->line_cb_phase[lineno][(2+phasecorr)&3]- |
---|
615 | it->line_cb_phase[lineno][(0+phasecorr)&3])/16.0; |
---|
616 | double cb_q=(it->line_cb_phase[lineno][(3+phasecorr)&3]- |
---|
617 | it->line_cb_phase[lineno][(1+phasecorr)&3])/16.0; |
---|
618 | |
---|
619 | colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8; |
---|
620 | |
---|
621 | if (colormode) { |
---|
622 | double tint_i = -cos((103 + it->color_control)*3.1415926/180); |
---|
623 | double tint_q = sin((103 + it->color_control)*3.1415926/180); |
---|
624 | |
---|
625 | multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control; |
---|
626 | multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control; |
---|
627 | multiq2[2]=-multiq2[0]; |
---|
628 | multiq2[3]=-multiq2[1]; |
---|
629 | } |
---|
630 | } |
---|
631 | |
---|
632 | #if 0 |
---|
633 | if (lineno==100) { |
---|
634 | printf("multiq = [%0.3f %0.3f %0.3f %0.3f] ", |
---|
635 | it->multiq[60],it->multiq[61],it->multiq[62],it->multiq[63]); |
---|
636 | printf("it->line_cb_phase = [%0.3f %0.3f %0.3f %0.3f]\n", |
---|
637 | it->line_cb_phase[lineno][0],it->line_cb_phase[lineno][1], |
---|
638 | it->line_cb_phase[lineno][2],it->line_cb_phase[lineno][3]); |
---|
639 | printf("multiq2 = [%0.3f %0.3f %0.3f %0.3f]\n", |
---|
640 | multiq2[0],multiq2[1],multiq2[2],multiq2[3]); |
---|
641 | } |
---|
642 | #endif |
---|
643 | |
---|
644 | dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; |
---|
645 | for (i=0; i<5; i++) dp[i]=0.0; |
---|
646 | |
---|
647 | assert(start>=0); |
---|
648 | assert(end < ANALOGTV_PIC_LEN+10); |
---|
649 | |
---|
650 | dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; |
---|
651 | for (i=0; i<24; i++) dp[i]=0.0; |
---|
652 | for (i=start, yiq=it->yiq+start, sp=signal+start; |
---|
653 | i<end; |
---|
654 | i++, dp--, yiq++, sp++) { |
---|
655 | |
---|
656 | /* Now filter them. These are infinite impulse response filters |
---|
657 | calculated by the script at |
---|
658 | http://www-users.cs.york.ac.uk/~fisher/mkfilter. This is |
---|
659 | fixed-point integer DSP, son. No place for wimps. We do it in |
---|
660 | integer because you can count on integer being faster on most |
---|
661 | CPUs. We care about speed because we need to recalculate every |
---|
662 | time we blink text, and when we spew random bytes into screen |
---|
663 | memory. This is roughly 16.16 fixed point arithmetic, but we |
---|
664 | scale some filter values up by a few bits to avoid some nasty |
---|
665 | precision errors. */ |
---|
666 | |
---|
667 | /* Filter Y with a 4-pole low-pass Butterworth filter at 3.5 MHz |
---|
668 | with an extra zero at 3.5 MHz, from |
---|
669 | mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l |
---|
670 | Delay about 2 */ |
---|
671 | |
---|
672 | dp[0] = sp[0] * 0.0469904257251935 * agclevel; |
---|
673 | dp[8] = (+1.0*(dp[6]+dp[0]) |
---|
674 | +4.0*(dp[5]+dp[1]) |
---|
675 | +7.0*(dp[4]+dp[2]) |
---|
676 | +8.0*(dp[3]) |
---|
677 | -0.0176648*dp[12] |
---|
678 | -0.4860288*dp[10]); |
---|
679 | yiq->y = dp[8] + brightadd; |
---|
680 | } |
---|
681 | |
---|
682 | if (colormode) { |
---|
683 | dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; |
---|
684 | for (i=0; i<27; i++) dp[i]=0.0; |
---|
685 | |
---|
686 | for (i=start, yiq=it->yiq+start, sp=signal+start; |
---|
687 | i<end; |
---|
688 | i++, dp--, yiq++, sp++) { |
---|
689 | double sig=*sp; |
---|
690 | |
---|
691 | /* Filter I and Q with a 3-pole low-pass Butterworth filter at |
---|
692 | 1.5 MHz with an extra zero at 3.5 MHz, from |
---|
693 | mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 -Z 2.5000000000e-01 -l |
---|
694 | Delay about 3. |
---|
695 | */ |
---|
696 | |
---|
697 | dp[0] = sig*multiq2[i&3] * 0.0833333333333; |
---|
698 | yiq->i=dp[8] = (dp[5] + dp[0] |
---|
699 | +3.0*(dp[4] + dp[1]) |
---|
700 | +4.0*(dp[3] + dp[2]) |
---|
701 | -0.3333333333 * dp[10]); |
---|
702 | |
---|
703 | dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333; |
---|
704 | yiq->q=dp[24] = (dp[16+5] + dp[16+0] |
---|
705 | +3.0*(dp[16+4] + dp[16+1]) |
---|
706 | +4.0*(dp[16+3] + dp[16+2]) |
---|
707 | -0.3333333333 * dp[24+2]); |
---|
708 | } |
---|
709 | } else { |
---|
710 | for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) { |
---|
711 | yiq->i = yiq->q = 0.0; |
---|
712 | } |
---|
713 | } |
---|
714 | } |
---|
715 | |
---|
716 | void |
---|
717 | analogtv_setup_teletext(analogtv_input *input) |
---|
718 | { |
---|
719 | int x,y; |
---|
720 | int teletext=ANALOGTV_BLACK_LEVEL; |
---|
721 | |
---|
722 | /* Teletext goes in line 21. But I suspect there are other things |
---|
723 | in the vertical retrace interval */ |
---|
724 | |
---|
725 | for (y=19; y<22; y++) { |
---|
726 | for (x=ANALOGTV_PIC_START; x<ANALOGTV_PIC_END; x++) { |
---|
727 | if ((x&7)==0) { |
---|
728 | teletext=(random()&1) ? ANALOGTV_WHITE_LEVEL : ANALOGTV_BLACK_LEVEL; |
---|
729 | } |
---|
730 | input->signal[y][x]=teletext; |
---|
731 | } |
---|
732 | } |
---|
733 | } |
---|
734 | |
---|
735 | void |
---|
736 | analogtv_setup_frame(analogtv *it) |
---|
737 | { |
---|
738 | int i,x,y; |
---|
739 | |
---|
740 | it->redraw_all=0; |
---|
741 | |
---|
742 | if (it->flutter_horiz_desync) { |
---|
743 | /* Horizontal sync during vertical sync instability. */ |
---|
744 | it->horiz_desync += -0.10*(it->horiz_desync-3.0) + |
---|
745 | ((int)(random()&0xff)-0x80) * |
---|
746 | ((int)(random()&0xff)-0x80) * |
---|
747 | ((int)(random()&0xff)-0x80) * 0.000001; |
---|
748 | } |
---|
749 | |
---|
750 | for (i=0; i<ANALOGTV_V; i++) { |
---|
751 | it->hashnoise_times[i]=0; |
---|
752 | } |
---|
753 | |
---|
754 | if (it->hashnoise_enable && !it->hashnoise_on) { |
---|
755 | if (random()%10000==0) { |
---|
756 | it->hashnoise_on=1; |
---|
757 | it->shrinkpulse=random()%ANALOGTV_V; |
---|
758 | } |
---|
759 | } |
---|
760 | if (random()%1000==0) { |
---|
761 | it->hashnoise_on=0; |
---|
762 | } |
---|
763 | if (it->hashnoise_on) { |
---|
764 | it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 + |
---|
765 | ((int)(random()%2000)-1000)*0.1; |
---|
766 | } else { |
---|
767 | it->hashnoise_rpm -= 100 + 0.01*it->hashnoise_rpm; |
---|
768 | if (it->hashnoise_rpm<0.0) it->hashnoise_rpm=0.0; |
---|
769 | } |
---|
770 | if (it->hashnoise_rpm >= 0.0) { |
---|
771 | int hni; |
---|
772 | int hnc=it->hashnoise_counter; /* in 24.8 format */ |
---|
773 | |
---|
774 | /* Convert rpm of a 16-pole motor into dots in 24.8 format */ |
---|
775 | hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 / |
---|
776 | (it->hashnoise_rpm * 16.0 / 60.0 / 60.0)); |
---|
777 | |
---|
778 | while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) { |
---|
779 | y=(hnc>>8)/ANALOGTV_H; |
---|
780 | x=(hnc>>8)%ANALOGTV_H; |
---|
781 | |
---|
782 | if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) { |
---|
783 | it->hashnoise_times[y]=x; |
---|
784 | } |
---|
785 | hnc += hni + (int)(random()%65536)-32768; |
---|
786 | } |
---|
787 | hnc -= (ANALOGTV_V * ANALOGTV_H)<<8; |
---|
788 | } |
---|
789 | |
---|
790 | it->agclevel = 1.0/it->rx_signal_level; |
---|
791 | |
---|
792 | |
---|
793 | #ifdef DEBUG2 |
---|
794 | printf("filter: "); |
---|
795 | for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) { |
---|
796 | printf(" %0.3f",it->ghostfir[i]); |
---|
797 | } |
---|
798 | printf(" siglevel=%g agc=%g\n", siglevel, it->agclevel); |
---|
799 | #endif |
---|
800 | } |
---|
801 | |
---|
802 | void |
---|
803 | analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi) |
---|
804 | { |
---|
805 | int i,lineno,vsync; |
---|
806 | char *sig; |
---|
807 | |
---|
808 | int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL; |
---|
809 | |
---|
810 | for (lineno=0; lineno<ANALOGTV_V; lineno++) { |
---|
811 | vsync=lineno>=3 && lineno<7; |
---|
812 | |
---|
813 | sig=input->signal[lineno]; |
---|
814 | |
---|
815 | i=ANALOGTV_SYNC_START; |
---|
816 | if (vsync) { |
---|
817 | while (i<ANALOGTV_BP_START) sig[i++]=ANALOGTV_BLANK_LEVEL; |
---|
818 | while (i<ANALOGTV_H) sig[i++]=synclevel; |
---|
819 | } else { |
---|
820 | while (i<ANALOGTV_BP_START) sig[i++]=synclevel; |
---|
821 | while (i<ANALOGTV_PIC_START) sig[i++]=ANALOGTV_BLANK_LEVEL; |
---|
822 | while (i<ANALOGTV_FP_START) sig[i++]=ANALOGTV_BLACK_LEVEL; |
---|
823 | } |
---|
824 | while (i<ANALOGTV_H) sig[i++]=ANALOGTV_BLANK_LEVEL; |
---|
825 | |
---|
826 | if (do_cb) { |
---|
827 | /* 9 cycles of colorburst */ |
---|
828 | for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) { |
---|
829 | sig[i+1] += ANALOGTV_CB_LEVEL; |
---|
830 | sig[i+3] -= ANALOGTV_CB_LEVEL; |
---|
831 | } |
---|
832 | } |
---|
833 | } |
---|
834 | } |
---|
835 | |
---|
836 | void |
---|
837 | analogtv_sync(analogtv *it) |
---|
838 | { |
---|
839 | int cur_hsync=it->cur_hsync; |
---|
840 | int cur_vsync=it->cur_vsync; |
---|
841 | int lineno; |
---|
842 | int i,j; |
---|
843 | double osc,filt; |
---|
844 | double *sp; |
---|
845 | double cbfc=1.0/128.0; |
---|
846 | |
---|
847 | sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync; |
---|
848 | for (i=-32; i<32; i++) { |
---|
849 | lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V; |
---|
850 | sp = it->rx_signal + lineno*ANALOGTV_H; |
---|
851 | filt=0.0; |
---|
852 | for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) { |
---|
853 | filt += sp[j]; |
---|
854 | } |
---|
855 | filt *= it->agclevel; |
---|
856 | |
---|
857 | osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V; |
---|
858 | |
---|
859 | if (osc >= 1.05+0.0002 * filt) break; |
---|
860 | } |
---|
861 | cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V; |
---|
862 | |
---|
863 | for (lineno=0; lineno<ANALOGTV_V; lineno++) { |
---|
864 | |
---|
865 | if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */ |
---|
866 | |
---|
867 | sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V |
---|
868 | )*ANALOGTV_H + cur_hsync; |
---|
869 | for (i=-8; i<8; i++) { |
---|
870 | osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H; |
---|
871 | filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel; |
---|
872 | |
---|
873 | if (osc >= 1.005 + 0.0001*filt) break; |
---|
874 | } |
---|
875 | cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H; |
---|
876 | } |
---|
877 | |
---|
878 | it->line_hsync[lineno]=(cur_hsync + ANALOGTV_PIC_START + |
---|
879 | ANALOGTV_H) % ANALOGTV_H; |
---|
880 | |
---|
881 | /* Now look for the colorburst, which is a few cycles after the H |
---|
882 | sync pulse, and store its phase. |
---|
883 | The colorburst is 9 cycles long, and we look at the middle 5 |
---|
884 | cycles. |
---|
885 | */ |
---|
886 | |
---|
887 | if (lineno>15) { |
---|
888 | sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3); |
---|
889 | for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) { |
---|
890 | it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) + |
---|
891 | sp[i]*it->agclevel*cbfc; |
---|
892 | } |
---|
893 | } |
---|
894 | |
---|
895 | { |
---|
896 | double tot=0.1; |
---|
897 | double cbgain; |
---|
898 | |
---|
899 | for (i=0; i<4; i++) { |
---|
900 | tot += it->cb_phase[i]*it->cb_phase[i]; |
---|
901 | } |
---|
902 | cbgain = 32.0/sqrt(tot); |
---|
903 | |
---|
904 | for (i=0; i<4; i++) { |
---|
905 | it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain; |
---|
906 | } |
---|
907 | } |
---|
908 | |
---|
909 | #ifdef DEBUG |
---|
910 | if (0) printf("hs=%d cb=[%0.3f %0.3f %0.3f %0.3f]\n", |
---|
911 | cur_hsync, |
---|
912 | it->cb_phase[0], it->cb_phase[1], |
---|
913 | it->cb_phase[2], it->cb_phase[3]); |
---|
914 | #endif |
---|
915 | |
---|
916 | /* if (random()%2000==0) cur_hsync=random()%ANALOGTV_H; */ |
---|
917 | } |
---|
918 | |
---|
919 | it->cur_hsync = cur_hsync; |
---|
920 | it->cur_vsync = cur_vsync; |
---|
921 | } |
---|
922 | |
---|
923 | static double |
---|
924 | analogtv_levelmult(analogtv *it, int level) |
---|
925 | { |
---|
926 | static double levelfac[3]={-7.5, 5.5, 24.5}; |
---|
927 | return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0; |
---|
928 | } |
---|
929 | |
---|
930 | static int |
---|
931 | analogtv_level(analogtv *it, int y, int ytop, int ybot) |
---|
932 | { |
---|
933 | int level; |
---|
934 | if (ybot-ytop>=7) { |
---|
935 | if (y==ytop || y==ybot-1) level=0; |
---|
936 | else if (y==ytop+1 || y==ybot-2) level=1; |
---|
937 | else level=2; |
---|
938 | } |
---|
939 | else if (ybot-ytop>=5) { |
---|
940 | if (y==ytop || y==ybot-1) level=0; |
---|
941 | else level=2; |
---|
942 | } |
---|
943 | else if (ybot-ytop>=3) { |
---|
944 | if (y==ytop) level=0; |
---|
945 | else level=2; |
---|
946 | } |
---|
947 | else { |
---|
948 | level=2; |
---|
949 | } |
---|
950 | return level; |
---|
951 | } |
---|
952 | |
---|
953 | static void |
---|
954 | analogtv_blast_imagerow(analogtv *it, |
---|
955 | float *rgbf, float *rgbf_end, |
---|
956 | int ytop, int ybot) |
---|
957 | { |
---|
958 | int i,j,x,y; |
---|
959 | float *rpf; |
---|
960 | char *level_copyfrom[3]; |
---|
961 | int xrepl=it->xrepl; |
---|
962 | for (i=0; i<3; i++) level_copyfrom[i]=NULL; |
---|
963 | |
---|
964 | for (y=ytop; y<ybot; y++) { |
---|
965 | int level=analogtv_level(it, y, ytop, ybot); |
---|
966 | char *rowdata; |
---|
967 | |
---|
968 | rowdata=it->image->data + y*it->image->bytes_per_line; |
---|
969 | |
---|
970 | /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show |
---|
971 | why standard graphics sw has to be fast, or else people will have to |
---|
972 | work around it and risk incompatibility. The quickdraw folks |
---|
973 | understood this. The other answer would be for X11 to have fewer |
---|
974 | formats for bitm.. oh, never mind. If neither of these cases work |
---|
975 | (they probably cover 99% of setups) it falls back on the Xlib |
---|
976 | routines. */ |
---|
977 | |
---|
978 | if (level_copyfrom[level]) { |
---|
979 | memcpy(rowdata, level_copyfrom[level], it->image->bytes_per_line); |
---|
980 | } |
---|
981 | else { |
---|
982 | double levelmult=analogtv_levelmult(it, level); |
---|
983 | level_copyfrom[level] = rowdata; |
---|
984 | |
---|
985 | if (0) { |
---|
986 | } |
---|
987 | else if (it->image->format==ZPixmap && |
---|
988 | it->image->bits_per_pixel==32 && |
---|
989 | sizeof(unsigned int)==4 && |
---|
990 | it->image->byte_order==localbyteorder) { |
---|
991 | /* int is more likely to be 32 bits than long */ |
---|
992 | unsigned int *pixelptr=(unsigned int *)rowdata; |
---|
993 | unsigned int pix; |
---|
994 | |
---|
995 | for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) { |
---|
996 | int ntscri=rpf[0]*levelmult; |
---|
997 | int ntscgi=rpf[1]*levelmult; |
---|
998 | int ntscbi=rpf[2]*levelmult; |
---|
999 | if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1; |
---|
1000 | if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1; |
---|
1001 | if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1; |
---|
1002 | pix = (it->red_values[ntscri] | |
---|
1003 | it->green_values[ntscgi] | |
---|
1004 | it->blue_values[ntscbi]); |
---|
1005 | pixelptr[0] = pix; |
---|
1006 | if (xrepl>=2) { |
---|
1007 | pixelptr[1] = pix; |
---|
1008 | if (xrepl>=3) pixelptr[2] = pix; |
---|
1009 | } |
---|
1010 | pixelptr+=xrepl; |
---|
1011 | } |
---|
1012 | } |
---|
1013 | else if (it->image->format==ZPixmap && |
---|
1014 | it->image->bits_per_pixel==16 && |
---|
1015 | sizeof(unsigned short)==2 && |
---|
1016 | float_extraction_works && |
---|
1017 | it->image->byte_order==localbyteorder) { |
---|
1018 | unsigned short *pixelptr=(unsigned short *)rowdata; |
---|
1019 | double r2,g2,b2; |
---|
1020 | float_extract_t r1,g1,b1; |
---|
1021 | unsigned short pix; |
---|
1022 | |
---|
1023 | for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) { |
---|
1024 | r2=rpf[0]; g2=rpf[1]; b2=rpf[2]; |
---|
1025 | r1.f=r2 * levelmult+float_low8_ofs; |
---|
1026 | g1.f=g2 * levelmult+float_low8_ofs; |
---|
1027 | b1.f=b2 * levelmult+float_low8_ofs; |
---|
1028 | pix = (it->red_values[r1.i & 0x3ff] | |
---|
1029 | it->green_values[g1.i & 0x3ff] | |
---|
1030 | it->blue_values[b1.i & 0x3ff]); |
---|
1031 | pixelptr[0] = pix; |
---|
1032 | if (xrepl>=2) { |
---|
1033 | pixelptr[1] = pix; |
---|
1034 | if (xrepl>=3) pixelptr[2] = pix; |
---|
1035 | } |
---|
1036 | pixelptr+=xrepl; |
---|
1037 | } |
---|
1038 | } |
---|
1039 | else if (it->image->format==ZPixmap && |
---|
1040 | it->image->bits_per_pixel==16 && |
---|
1041 | sizeof(unsigned short)==2 && |
---|
1042 | it->image->byte_order==localbyteorder) { |
---|
1043 | unsigned short *pixelptr=(unsigned short *)rowdata; |
---|
1044 | unsigned short pix; |
---|
1045 | |
---|
1046 | for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) { |
---|
1047 | int r1=rpf[0] * levelmult; |
---|
1048 | int g1=rpf[1] * levelmult; |
---|
1049 | int b1=rpf[2] * levelmult; |
---|
1050 | if (r1>=ANALOGTV_CV_MAX) r1=ANALOGTV_CV_MAX-1; |
---|
1051 | if (g1>=ANALOGTV_CV_MAX) g1=ANALOGTV_CV_MAX-1; |
---|
1052 | if (b1>=ANALOGTV_CV_MAX) b1=ANALOGTV_CV_MAX-1; |
---|
1053 | pix = it->red_values[r1] | it->green_values[g1] | it->blue_values[b1]; |
---|
1054 | pixelptr[0] = pix; |
---|
1055 | if (xrepl>=2) { |
---|
1056 | pixelptr[1] = pix; |
---|
1057 | if (xrepl>=3) pixelptr[2] = pix; |
---|
1058 | } |
---|
1059 | pixelptr+=xrepl; |
---|
1060 | } |
---|
1061 | } |
---|
1062 | else { |
---|
1063 | for (x=0, rpf=rgbf; rpf!=rgbf_end ; x++, rpf+=3) { |
---|
1064 | int ntscri=rpf[0]*levelmult; |
---|
1065 | int ntscgi=rpf[1]*levelmult; |
---|
1066 | int ntscbi=rpf[2]*levelmult; |
---|
1067 | if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1; |
---|
1068 | if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1; |
---|
1069 | if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1; |
---|
1070 | for (j=0; j<xrepl; j++) { |
---|
1071 | XPutPixel(it->image, x*xrepl + j, y, |
---|
1072 | it->red_values[ntscri] | it->green_values[ntscgi] | |
---|
1073 | it->blue_values[ntscbi]); |
---|
1074 | } |
---|
1075 | } |
---|
1076 | } |
---|
1077 | } |
---|
1078 | } |
---|
1079 | } |
---|
1080 | |
---|
1081 | void |
---|
1082 | analogtv_draw(analogtv *it) |
---|
1083 | { |
---|
1084 | int i,j,x,y,lineno; |
---|
1085 | int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate; |
---|
1086 | float *rgb_start, *rgb_end; |
---|
1087 | double pixbright; |
---|
1088 | int pixmultinc; |
---|
1089 | int bigloadchange,drawcount; |
---|
1090 | double baseload; |
---|
1091 | double puheight; |
---|
1092 | int overall_top, overall_bot; |
---|
1093 | |
---|
1094 | float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float)); |
---|
1095 | float *raw_rgb_end=raw_rgb_start+3*it->subwidth; |
---|
1096 | float *rrp; |
---|
1097 | |
---|
1098 | analogtv_setup_frame(it); |
---|
1099 | analogtv_set_demod(it); |
---|
1100 | |
---|
1101 | /* rx_signal has an extra 2 lines at the end, where we copy the |
---|
1102 | first 2 lines so we can index into it while only worrying about |
---|
1103 | wraparound on a per-line level */ |
---|
1104 | memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN], |
---|
1105 | &it->rx_signal[0], |
---|
1106 | 2*ANALOGTV_H*sizeof(it->rx_signal[0])); |
---|
1107 | |
---|
1108 | analogtv_sync(it); |
---|
1109 | |
---|
1110 | baseload=0.5; |
---|
1111 | /* if (it->hashnoise_on) baseload=0.5; */ |
---|
1112 | |
---|
1113 | bigloadchange=1; |
---|
1114 | drawcount=0; |
---|
1115 | it->crtload[ANALOGTV_TOP-1]=baseload; |
---|
1116 | puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control * |
---|
1117 | (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1)); |
---|
1118 | |
---|
1119 | overall_top=it->useheight; |
---|
1120 | overall_bot=0; |
---|
1121 | |
---|
1122 | for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) { |
---|
1123 | int slineno=lineno-ANALOGTV_TOP; |
---|
1124 | int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES - |
---|
1125 | it->useheight/2)*puheight) + it->useheight/2; |
---|
1126 | int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES - |
---|
1127 | it->useheight/2)*puheight) + it->useheight/2; |
---|
1128 | #if 0 |
---|
1129 | int linesig=analogtv_line_signature(input,lineno) |
---|
1130 | + it->hashnoise_times[lineno]; |
---|
1131 | #endif |
---|
1132 | double *signal=(it->rx_signal + ((lineno + it->cur_vsync + |
---|
1133 | ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H + |
---|
1134 | it->line_hsync[lineno]); |
---|
1135 | |
---|
1136 | if (ytop==ybot) continue; |
---|
1137 | if (ybot<0 || ytop>it->useheight) continue; |
---|
1138 | if (ytop<0) ytop=0; |
---|
1139 | if (ybot>it->useheight) ybot=it->useheight; |
---|
1140 | |
---|
1141 | if (ytop < overall_top) overall_top=ytop; |
---|
1142 | if (ybot > overall_bot) overall_bot=ybot; |
---|
1143 | |
---|
1144 | if (lineno==it->shrinkpulse) { |
---|
1145 | baseload += 0.4; |
---|
1146 | bigloadchange=1; |
---|
1147 | it->shrinkpulse=-1; |
---|
1148 | } |
---|
1149 | |
---|
1150 | #if 0 |
---|
1151 | if (it->hashnoise_rpm>0.0 && |
---|
1152 | !(bigloadchange || |
---|
1153 | it->redraw_all || |
---|
1154 | (slineno<20 && it->flutter_horiz_desync) || |
---|
1155 | it->gaussiannoise_level>30 || |
---|
1156 | ((it->gaussiannoise_level>2.0 || |
---|
1157 | it->multipath) && random()%4) || |
---|
1158 | linesig != it->onscreen_signature[lineno])) { |
---|
1159 | continue; |
---|
1160 | } |
---|
1161 | it->onscreen_signature[lineno] = linesig; |
---|
1162 | #endif |
---|
1163 | drawcount++; |
---|
1164 | |
---|
1165 | /* |
---|
1166 | Interpolate the 600-dotclock line into however many horizontal |
---|
1167 | screen pixels we're using, and convert to RGB. |
---|
1168 | |
---|
1169 | We add some 'bloom', variations in the horizontal scan width with |
---|
1170 | the amount of brightness, extremely common on period TV sets. They |
---|
1171 | had a single oscillator which generated both the horizontal scan and |
---|
1172 | (during the horizontal retrace interval) the high voltage for the |
---|
1173 | electron beam. More brightness meant more load on the oscillator, |
---|
1174 | which caused an decrease in horizontal deflection. Look for |
---|
1175 | (bloomthisrow). |
---|
1176 | |
---|
1177 | Also, the A2 did a bad job of generating horizontal sync pulses |
---|
1178 | during the vertical blanking interval. This, and the fact that the |
---|
1179 | horizontal frequency was a bit off meant that TVs usually went a bit |
---|
1180 | out of sync during the vertical retrace, and the top of the screen |
---|
1181 | would be bent a bit to the left or right. Look for (shiftthisrow). |
---|
1182 | |
---|
1183 | We also add a teeny bit of left overscan, just enough to be |
---|
1184 | annoying, but you can still read the left column of text. |
---|
1185 | |
---|
1186 | We also simulate compression & brightening on the right side of the |
---|
1187 | screen. Most TVs do this, but you don't notice because they overscan |
---|
1188 | so it's off the right edge of the CRT. But the A2 video system used |
---|
1189 | so much of the horizontal scan line that you had to crank the |
---|
1190 | horizontal width down in order to not lose the right few characters, |
---|
1191 | and you'd see the compression on the right edge. Associated with |
---|
1192 | compression is brightening; since the electron beam was scanning |
---|
1193 | slower, the same drive signal hit the phosphor harder. Look for |
---|
1194 | (squishright_i) and (squishdiv). |
---|
1195 | */ |
---|
1196 | |
---|
1197 | { |
---|
1198 | int totsignal=0; |
---|
1199 | double ncl,diff; |
---|
1200 | |
---|
1201 | for (i=0; i<ANALOGTV_PIC_LEN; i++) { |
---|
1202 | totsignal += signal[i]; |
---|
1203 | } |
---|
1204 | totsignal *= it->agclevel; |
---|
1205 | ncl = 0.95 * it->crtload[lineno-1] + |
---|
1206 | 0.05*(baseload + |
---|
1207 | (totsignal-30000)/100000.0 + |
---|
1208 | (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom |
---|
1209 | : 0.0)); |
---|
1210 | diff=ncl - it->crtload[lineno]; |
---|
1211 | bigloadchange = (diff>0.01 || diff<-0.01); |
---|
1212 | it->crtload[lineno]=ncl; |
---|
1213 | } |
---|
1214 | |
---|
1215 | { |
---|
1216 | double bloomthisrow,shiftthisrow; |
---|
1217 | double viswidth,middle; |
---|
1218 | double scanwidth; |
---|
1219 | int scw,scl,scr; |
---|
1220 | |
---|
1221 | bloomthisrow = -10.0 * it->crtload[lineno]; |
---|
1222 | if (bloomthisrow<-10.0) bloomthisrow=-10.0; |
---|
1223 | if (bloomthisrow>2.0) bloomthisrow=2.0; |
---|
1224 | if (slineno<16) { |
---|
1225 | shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) * |
---|
1226 | (0.7+cos(slineno*0.6))); |
---|
1227 | } else { |
---|
1228 | shiftthisrow=0.0; |
---|
1229 | } |
---|
1230 | |
---|
1231 | viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow; |
---|
1232 | middle=ANALOGTV_PIC_LEN/2 - shiftthisrow; |
---|
1233 | |
---|
1234 | scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0); |
---|
1235 | |
---|
1236 | scw=it->subwidth*scanwidth; |
---|
1237 | if (scw>it->subwidth) scw=it->usewidth; |
---|
1238 | scl=it->subwidth/2 - scw/2; |
---|
1239 | scr=it->subwidth/2 + scw/2; |
---|
1240 | |
---|
1241 | pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth; |
---|
1242 | scanstart_i=(int)((middle-viswidth*0.5)*65536.0); |
---|
1243 | scanend_i=(ANALOGTV_PIC_LEN-1)*65536; |
---|
1244 | squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1) |
---|
1245 | - it->squish_control)) *65536.0); |
---|
1246 | squishdiv=it->subwidth/15; |
---|
1247 | |
---|
1248 | rgb_start=raw_rgb_start+scl*3; |
---|
1249 | rgb_end=raw_rgb_start+scr*3; |
---|
1250 | |
---|
1251 | assert(scanstart_i>=0); |
---|
1252 | |
---|
1253 | #ifdef DEBUG |
---|
1254 | if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n", |
---|
1255 | lineno, |
---|
1256 | scanstart_i/65536.0, |
---|
1257 | squishright_i/65536.0, |
---|
1258 | scanend_i/65536.0, |
---|
1259 | scl,scr,scw); |
---|
1260 | #endif |
---|
1261 | } |
---|
1262 | |
---|
1263 | if (it->use_cmap) { |
---|
1264 | for (y=ytop; y<ybot; y++) { |
---|
1265 | int level=analogtv_level(it, y, ytop, ybot); |
---|
1266 | double levelmult=analogtv_levelmult(it, level); |
---|
1267 | double levelmult_y = levelmult * it->contrast_control |
---|
1268 | * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070; |
---|
1269 | double levelmult_iq = levelmult * 0.090; |
---|
1270 | |
---|
1271 | struct analogtv_yiq_s *yiq=it->yiq; |
---|
1272 | analogtv_ntsc_to_yiq(it, lineno, signal, |
---|
1273 | (scanstart_i>>16)-10, (scanend_i>>16)+10); |
---|
1274 | pixmultinc=pixrate; |
---|
1275 | |
---|
1276 | x=0; |
---|
1277 | i=scanstart_i; |
---|
1278 | while (i<0 && x<it->usewidth) { |
---|
1279 | XPutPixel(it->image, x, y, it->colors[0]); |
---|
1280 | i+=pixmultinc; |
---|
1281 | x++; |
---|
1282 | } |
---|
1283 | |
---|
1284 | while (i<scanend_i && x<it->usewidth) { |
---|
1285 | double pixfrac=(i&0xffff)/65536.0; |
---|
1286 | double invpixfrac=(1.0-pixfrac); |
---|
1287 | int pati=i>>16; |
---|
1288 | int yli,ili,qli,cmi; |
---|
1289 | |
---|
1290 | double interpy=(yiq[pati].y*invpixfrac |
---|
1291 | + yiq[pati+1].y*pixfrac) * levelmult_y; |
---|
1292 | double interpi=(yiq[pati].i*invpixfrac |
---|
1293 | + yiq[pati+1].i*pixfrac) * levelmult_iq; |
---|
1294 | double interpq=(yiq[pati].q*invpixfrac |
---|
1295 | + yiq[pati+1].q*pixfrac) * levelmult_iq; |
---|
1296 | |
---|
1297 | yli = (int)(interpy * it->cmap_y_levels); |
---|
1298 | ili = (int)((interpi+0.5) * it->cmap_i_levels); |
---|
1299 | qli = (int)((interpq+0.5) * it->cmap_q_levels); |
---|
1300 | if (yli<0) yli=0; |
---|
1301 | if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1; |
---|
1302 | if (ili<0) ili=0; |
---|
1303 | if (ili>=it->cmap_i_levels) ili=it->cmap_i_levels-1; |
---|
1304 | if (qli<0) qli=0; |
---|
1305 | if (qli>=it->cmap_q_levels) qli=it->cmap_q_levels-1; |
---|
1306 | |
---|
1307 | cmi=qli + it->cmap_i_levels*(ili + it->cmap_q_levels*yli); |
---|
1308 | |
---|
1309 | #ifdef DEBUG |
---|
1310 | if ((random()%65536)==0) { |
---|
1311 | printf("%0.3f %0.3f %0.3f => %d %d %d => %d\n", |
---|
1312 | interpy, interpi, interpq, |
---|
1313 | yli, ili, qli, |
---|
1314 | cmi); |
---|
1315 | } |
---|
1316 | #endif |
---|
1317 | |
---|
1318 | for (j=0; j<it->xrepl; j++) { |
---|
1319 | XPutPixel(it->image, x, y, |
---|
1320 | it->colors[cmi]); |
---|
1321 | x++; |
---|
1322 | } |
---|
1323 | if (i >= squishright_i) { |
---|
1324 | pixmultinc += pixmultinc/squishdiv; |
---|
1325 | } |
---|
1326 | i+=pixmultinc; |
---|
1327 | } |
---|
1328 | while (x<it->usewidth) { |
---|
1329 | XPutPixel(it->image, x, y, it->colors[0]); |
---|
1330 | x++; |
---|
1331 | } |
---|
1332 | } |
---|
1333 | } |
---|
1334 | else { |
---|
1335 | struct analogtv_yiq_s *yiq=it->yiq; |
---|
1336 | analogtv_ntsc_to_yiq(it, lineno, signal, |
---|
1337 | (scanstart_i>>16)-10, (scanend_i>>16)+10); |
---|
1338 | |
---|
1339 | pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0) |
---|
1340 | / (0.5+0.5*puheight) * 1024.0/100.0; |
---|
1341 | pixmultinc=pixrate; |
---|
1342 | i=scanstart_i; rrp=rgb_start; |
---|
1343 | while (i<0 && rrp!=rgb_end) { |
---|
1344 | rrp[0]=rrp[1]=rrp[2]=0; |
---|
1345 | i+=pixmultinc; |
---|
1346 | rrp+=3; |
---|
1347 | } |
---|
1348 | while (i<scanend_i && rrp!=rgb_end) { |
---|
1349 | double pixfrac=(i&0xffff)/65536.0; |
---|
1350 | double invpixfrac=1.0-pixfrac; |
---|
1351 | int pati=i>>16; |
---|
1352 | double r,g,b; |
---|
1353 | |
---|
1354 | double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac); |
---|
1355 | double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac); |
---|
1356 | double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac); |
---|
1357 | |
---|
1358 | /* |
---|
1359 | According to the NTSC spec, Y,I,Q are generated as: |
---|
1360 | |
---|
1361 | y=0.30 r + 0.59 g + 0.11 b |
---|
1362 | i=0.60 r - 0.28 g - 0.32 b |
---|
1363 | q=0.21 r - 0.52 g + 0.31 b |
---|
1364 | |
---|
1365 | So if you invert the implied 3x3 matrix you get what standard |
---|
1366 | televisions implement with a bunch of resistors (or directly in the |
---|
1367 | CRT -- don't ask): |
---|
1368 | |
---|
1369 | r = y + 0.948 i + 0.624 q |
---|
1370 | g = y - 0.276 i - 0.639 q |
---|
1371 | b = y - 1.105 i + 1.729 q |
---|
1372 | */ |
---|
1373 | |
---|
1374 | r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright; |
---|
1375 | g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright; |
---|
1376 | b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright; |
---|
1377 | if (r<0.0) r=0.0; |
---|
1378 | if (g<0.0) g=0.0; |
---|
1379 | if (b<0.0) b=0.0; |
---|
1380 | rrp[0]=r; |
---|
1381 | rrp[1]=g; |
---|
1382 | rrp[2]=b; |
---|
1383 | |
---|
1384 | if (i>=squishright_i) { |
---|
1385 | pixmultinc += pixmultinc/squishdiv; |
---|
1386 | pixbright += pixbright/squishdiv/2; |
---|
1387 | } |
---|
1388 | i+=pixmultinc; |
---|
1389 | rrp+=3; |
---|
1390 | } |
---|
1391 | while (rrp != rgb_end) { |
---|
1392 | rrp[0]=rrp[1]=rrp[2]=0.0; |
---|
1393 | rrp+=3; |
---|
1394 | } |
---|
1395 | |
---|
1396 | analogtv_blast_imagerow(it, raw_rgb_start, raw_rgb_end, |
---|
1397 | ytop,ybot); |
---|
1398 | } |
---|
1399 | } |
---|
1400 | free(raw_rgb_start); |
---|
1401 | |
---|
1402 | #if 0 |
---|
1403 | /* poor attempt at visible retrace */ |
---|
1404 | for (i=0; i<15; i++) { |
---|
1405 | int ytop=(int)((i*it->useheight/15 - |
---|
1406 | it->useheight/2)*puheight) + it->useheight/2; |
---|
1407 | int ybot=(int)(((i+1)*it->useheight/15 - |
---|
1408 | it->useheight/2)*puheight) + it->useheight/2; |
---|
1409 | int div=it->usewidth*3/2; |
---|
1410 | |
---|
1411 | for (x=0; x<it->usewidth; x++) { |
---|
1412 | y = ytop + (ybot-ytop)*x / div; |
---|
1413 | if (y<0 || y>=it->useheight) continue; |
---|
1414 | XPutPixel(it->image, x, y, 0xffffff); |
---|
1415 | } |
---|
1416 | } |
---|
1417 | #endif |
---|
1418 | |
---|
1419 | if (it->need_clear) { |
---|
1420 | XClearWindow(it->dpy, it->window); |
---|
1421 | it->need_clear=0; |
---|
1422 | } |
---|
1423 | |
---|
1424 | if (overall_top>0) { |
---|
1425 | XClearArea(it->dpy, it->window, |
---|
1426 | it->screen_xo, it->screen_yo, |
---|
1427 | it->usewidth, overall_top, 0); |
---|
1428 | } |
---|
1429 | if (it->useheight > overall_bot) { |
---|
1430 | XClearArea(it->dpy, it->window, |
---|
1431 | it->screen_xo, it->screen_yo+overall_bot, |
---|
1432 | it->usewidth, it->useheight-overall_bot, 0); |
---|
1433 | } |
---|
1434 | |
---|
1435 | if (overall_bot > overall_top) { |
---|
1436 | if (it->use_shm) { |
---|
1437 | #ifdef HAVE_XSHM_EXTENSION |
---|
1438 | XShmPutImage(it->dpy, it->window, it->gc, it->image, |
---|
1439 | 0, overall_top, |
---|
1440 | it->screen_xo, it->screen_yo+overall_top, |
---|
1441 | it->usewidth, overall_bot - overall_top, |
---|
1442 | False); |
---|
1443 | #endif |
---|
1444 | } else { |
---|
1445 | XPutImage(it->dpy, it->window, it->gc, it->image, |
---|
1446 | 0, overall_top, |
---|
1447 | it->screen_xo, it->screen_yo+overall_top, |
---|
1448 | it->usewidth, overall_bot - overall_top); |
---|
1449 | } |
---|
1450 | } |
---|
1451 | |
---|
1452 | #ifdef DEBUG |
---|
1453 | if (0) { |
---|
1454 | struct timeval tv; |
---|
1455 | double fps; |
---|
1456 | char buf[256]; |
---|
1457 | gettimeofday(&tv,NULL); |
---|
1458 | |
---|
1459 | fps=1.0/((tv.tv_sec - it->last_display_time.tv_sec) |
---|
1460 | + 0.000001*(tv.tv_usec - it->last_display_time.tv_usec)); |
---|
1461 | sprintf(buf, "FPS=%0.1f",fps); |
---|
1462 | XDrawString(it->dpy, it->window, it->gc, 50, it->useheight*2/3, |
---|
1463 | buf, strlen(buf)); |
---|
1464 | |
---|
1465 | it->last_display_time=tv; |
---|
1466 | } |
---|
1467 | #endif |
---|
1468 | |
---|
1469 | XSync(it->dpy,0); |
---|
1470 | } |
---|
1471 | |
---|
1472 | analogtv_input * |
---|
1473 | analogtv_input_allocate() |
---|
1474 | { |
---|
1475 | analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input)); |
---|
1476 | |
---|
1477 | return ret; |
---|
1478 | } |
---|
1479 | |
---|
1480 | /* |
---|
1481 | This takes a screen image and encodes it as a video camera would, |
---|
1482 | including all the bandlimiting and YIQ modulation. |
---|
1483 | This isn't especially tuned for speed. |
---|
1484 | */ |
---|
1485 | |
---|
1486 | int |
---|
1487 | analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im) |
---|
1488 | { |
---|
1489 | int i,x,y; |
---|
1490 | int img_w,img_h; |
---|
1491 | int fyx[7],fyy[7]; |
---|
1492 | int fix[4],fiy[4]; |
---|
1493 | int fqx[4],fqy[4]; |
---|
1494 | XColor col1[ANALOGTV_PIC_LEN]; |
---|
1495 | XColor col2[ANALOGTV_PIC_LEN]; |
---|
1496 | int multiq[ANALOGTV_PIC_LEN+4]; |
---|
1497 | |
---|
1498 | img_w=pic_im->width; |
---|
1499 | img_h=pic_im->height; |
---|
1500 | |
---|
1501 | for (i=0; i<ANALOGTV_PIC_LEN+4; i++) { |
---|
1502 | double phase=90.0-90.0*i; |
---|
1503 | double ampl=1.0; |
---|
1504 | multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl); |
---|
1505 | } |
---|
1506 | |
---|
1507 | for (y=0; y<ANALOGTV_VISLINES; y++) { |
---|
1508 | int picy1=(y*img_h)/ANALOGTV_VISLINES; |
---|
1509 | int picy2=(y*img_h+ANALOGTV_VISLINES/2)/ANALOGTV_VISLINES; |
---|
1510 | |
---|
1511 | for (x=0; x<ANALOGTV_PIC_LEN; x++) { |
---|
1512 | int picx=(x*img_w)/ANALOGTV_PIC_LEN; |
---|
1513 | col1[x].pixel=XGetPixel(pic_im, picx, picy1); |
---|
1514 | col2[x].pixel=XGetPixel(pic_im, picx, picy2); |
---|
1515 | } |
---|
1516 | XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN); |
---|
1517 | XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN); |
---|
1518 | |
---|
1519 | for (i=0; i<7; i++) fyx[i]=fyy[i]=0; |
---|
1520 | for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0; |
---|
1521 | |
---|
1522 | for (x=0; x<ANALOGTV_PIC_LEN; x++) { |
---|
1523 | int rawy,rawi,rawq; |
---|
1524 | int filty,filti,filtq; |
---|
1525 | int composite; |
---|
1526 | /* Compute YIQ as: |
---|
1527 | y=0.30 r + 0.59 g + 0.11 b |
---|
1528 | i=0.60 r - 0.28 g - 0.32 b |
---|
1529 | q=0.21 r - 0.52 g + 0.31 b |
---|
1530 | The coefficients below are in .4 format */ |
---|
1531 | |
---|
1532 | rawy=( 5*col1[x].red + 11*col1[x].green + 2*col1[x].blue + |
---|
1533 | 5*col2[x].red + 11*col2[x].green + 2*col2[x].blue)>>7; |
---|
1534 | rawi=(10*col1[x].red - 4*col1[x].green - 5*col1[x].blue + |
---|
1535 | 10*col2[x].red - 4*col2[x].green - 5*col2[x].blue)>>7; |
---|
1536 | rawq=( 3*col1[x].red - 8*col1[x].green + 5*col1[x].blue + |
---|
1537 | 3*col2[x].red - 8*col2[x].green + 5*col2[x].blue)>>7; |
---|
1538 | |
---|
1539 | /* Filter y at with a 4-pole low-pass Butterworth filter at 3.5 MHz |
---|
1540 | with an extra zero at 3.5 MHz, from |
---|
1541 | mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l */ |
---|
1542 | |
---|
1543 | fyx[0] = fyx[1]; fyx[1] = fyx[2]; fyx[2] = fyx[3]; |
---|
1544 | fyx[3] = fyx[4]; fyx[4] = fyx[5]; fyx[5] = fyx[6]; |
---|
1545 | fyx[6] = (rawy * 1897) >> 16; |
---|
1546 | fyy[0] = fyy[1]; fyy[1] = fyy[2]; fyy[2] = fyy[3]; |
---|
1547 | fyy[3] = fyy[4]; fyy[4] = fyy[5]; fyy[5] = fyy[6]; |
---|
1548 | fyy[6] = (fyx[0]+fyx[6]) + 4*(fyx[1]+fyx[5]) + 7*(fyx[2]+fyx[4]) + 8*fyx[3] |
---|
1549 | + ((-151*fyy[2] + 8115*fyy[3] - 38312*fyy[4] + 36586*fyy[5]) >> 16); |
---|
1550 | filty = fyy[6]; |
---|
1551 | |
---|
1552 | /* Filter I at 1.5 MHz. 3 pole Butterworth from |
---|
1553 | mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 */ |
---|
1554 | |
---|
1555 | fix[0] = fix[1]; fix[1] = fix[2]; fix[2] = fix[3]; |
---|
1556 | fix[3] = (rawi * 1413) >> 16; |
---|
1557 | fiy[0] = fiy[1]; fiy[1] = fiy[2]; fiy[2] = fiy[3]; |
---|
1558 | fiy[3] = (fix[0]+fix[3]) + 3*(fix[1]+fix[2]) |
---|
1559 | + ((16559*fiy[0] - 72008*fiy[1] + 109682*fiy[2]) >> 16); |
---|
1560 | filti = fiy[3]; |
---|
1561 | |
---|
1562 | /* Filter Q at 0.5 MHz. 3 pole Butterworth from |
---|
1563 | mkfilter -Bu -Lp -o 3 -a 3.5714285714e-02 0 -l */ |
---|
1564 | |
---|
1565 | fqx[0] = fqx[1]; fqx[1] = fqx[2]; fqx[2] = fqx[3]; |
---|
1566 | fqx[3] = (rawq * 75) >> 16; |
---|
1567 | fqy[0] = fqy[1]; fqy[1] = fqy[2]; fqy[2] = fqy[3]; |
---|
1568 | fqy[3] = (fqx[0]+fqx[3]) + 3 * (fqx[1]+fqx[2]) |
---|
1569 | + ((2612*fqy[0] - 9007*fqy[1] + 10453 * fqy[2]) >> 12); |
---|
1570 | filtq = fqy[3]; |
---|
1571 | |
---|
1572 | |
---|
1573 | composite = filty + ((multiq[x] * filti + multiq[x+3] * filtq)>>12); |
---|
1574 | composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL; |
---|
1575 | if (composite>125) composite=125; |
---|
1576 | if (composite<0) composite=0; |
---|
1577 | input->signal[y+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite; |
---|
1578 | } |
---|
1579 | } |
---|
1580 | |
---|
1581 | return 1; |
---|
1582 | } |
---|
1583 | |
---|
1584 | #if 0 |
---|
1585 | void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2) |
---|
1586 | { |
---|
1587 | int x,y,newsig; |
---|
1588 | int change=random()%ANALOGTV_V; |
---|
1589 | unsigned int fastrnd=random(); |
---|
1590 | double hso=(int)(random()%1000)-500; |
---|
1591 | int yofs=random()%ANALOGTV_V; |
---|
1592 | int noise; |
---|
1593 | |
---|
1594 | for (y=change; y<ANALOGTV_V; y++) { |
---|
1595 | int s2y=(y+yofs)%ANALOGTV_V; |
---|
1596 | int filt=0; |
---|
1597 | int noiselevel=60000 / (y-change+100); |
---|
1598 | |
---|
1599 | it->line_hsync[y] = s2->line_hsync[y] + (int)hso; |
---|
1600 | hso *= 0.9; |
---|
1601 | for (x=0; x<ANALOGTV_H; x++) { |
---|
1602 | FASTRND; |
---|
1603 | filt+= (-filt/16) + (int)(fastrnd&0xfff)-0x800; |
---|
1604 | noise=(filt*noiselevel)>>16; |
---|
1605 | newsig=s2->signal[s2y][x] + noise; |
---|
1606 | if (newsig>120) newsig=120; |
---|
1607 | if (newsig<0) newsig=0; |
---|
1608 | it->signal[y][x]=newsig; |
---|
1609 | } |
---|
1610 | } |
---|
1611 | s2->vsync=yofs; |
---|
1612 | } |
---|
1613 | #endif |
---|
1614 | |
---|
1615 | |
---|
1616 | void analogtv_add_signal(analogtv *it, analogtv_reception *rec) |
---|
1617 | { |
---|
1618 | analogtv_input *inp=rec->input; |
---|
1619 | double *ps=it->rx_signal; |
---|
1620 | double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN; |
---|
1621 | double *p=ps; |
---|
1622 | char *ss=&inp->signal[0][0]; |
---|
1623 | char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN; |
---|
1624 | char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN); |
---|
1625 | int i; |
---|
1626 | int ec=it->channel_change_cycles; |
---|
1627 | double level=rec->level; |
---|
1628 | double hfloss=rec->hfloss; |
---|
1629 | unsigned int fastrnd=random(); |
---|
1630 | double dp[8]; |
---|
1631 | |
---|
1632 | /* assert((se-ss)%4==0 && (se-s)%4==0); */ |
---|
1633 | |
---|
1634 | /* duplicate the first line into the Nth line to ease wraparound computation */ |
---|
1635 | memcpy(inp->signal[ANALOGTV_V], inp->signal[0], |
---|
1636 | ANALOGTV_H * sizeof(inp->signal[0][0])); |
---|
1637 | |
---|
1638 | for (i=0; i<8; i++) dp[i]=0.0; |
---|
1639 | |
---|
1640 | if (ec) { |
---|
1641 | double noise_ampl; |
---|
1642 | |
---|
1643 | /* Do a big noisy transition. We can make the transition noise of |
---|
1644 | high constant strength regardless of signal strength. |
---|
1645 | |
---|
1646 | There are two separate state machines. here, One is the noise |
---|
1647 | process and the other is the |
---|
1648 | |
---|
1649 | We don't bother with the FIR filter here |
---|
1650 | */ |
---|
1651 | |
---|
1652 | noise_ampl = 1.3; |
---|
1653 | |
---|
1654 | while (p!=pe && ec>0) { |
---|
1655 | |
---|
1656 | double sig0=(double)s[0]; |
---|
1657 | double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff); |
---|
1658 | fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu; |
---|
1659 | |
---|
1660 | p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl; |
---|
1661 | |
---|
1662 | noise_ampl *= 0.99995; |
---|
1663 | |
---|
1664 | p++; |
---|
1665 | s++; |
---|
1666 | if (s>=se) s=ss; |
---|
1667 | ec--; |
---|
1668 | } |
---|
1669 | |
---|
1670 | } |
---|
1671 | |
---|
1672 | while (p != pe) { |
---|
1673 | double sig0,sig1,sig2,sig3,sigr; |
---|
1674 | |
---|
1675 | sig0=(double)s[0]; |
---|
1676 | sig1=(double)s[1]; |
---|
1677 | sig2=(double)s[2]; |
---|
1678 | sig3=(double)s[3]; |
---|
1679 | |
---|
1680 | dp[0]=sig0+sig1+sig2+sig3; |
---|
1681 | |
---|
1682 | /* Get the video out signal, and add some ghosting, typical of RF |
---|
1683 | monitor cables. This corresponds to a pretty long cable, but |
---|
1684 | looks right to me. |
---|
1685 | */ |
---|
1686 | |
---|
1687 | sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] + |
---|
1688 | dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]); |
---|
1689 | dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0]; |
---|
1690 | |
---|
1691 | p[0] += (sig0+sigr + sig2*hfloss) * level; |
---|
1692 | p[1] += (sig1+sigr + sig3*hfloss) * level; |
---|
1693 | p[2] += (sig2+sigr + sig0*hfloss) * level; |
---|
1694 | p[3] += (sig3+sigr + sig1*hfloss) * level; |
---|
1695 | |
---|
1696 | p += 4; |
---|
1697 | s += 4; |
---|
1698 | if (s>=se) s = ss + (s-se); |
---|
1699 | } |
---|
1700 | |
---|
1701 | it->rx_signal_level = |
---|
1702 | sqrt(it->rx_signal_level * it->rx_signal_level + |
---|
1703 | (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] + |
---|
1704 | rec->ghostfir[2] + rec->ghostfir[3])))); |
---|
1705 | |
---|
1706 | |
---|
1707 | it->channel_change_cycles=0; |
---|
1708 | |
---|
1709 | } |
---|
1710 | |
---|
1711 | #ifdef FIXME |
---|
1712 | /* add hash */ |
---|
1713 | if (it->hashnoise_times[lineno]) { |
---|
1714 | int hnt=it->hashnoise_times[lineno] - input->line_hsync[lineno]; |
---|
1715 | |
---|
1716 | if (hnt>=0 && hnt<ANALOGTV_PIC_LEN) { |
---|
1717 | double maxampl=1.0; |
---|
1718 | double cur=frand(150.0)-20.0; |
---|
1719 | int len=random()%15+3; |
---|
1720 | if (len > ANALOGTV_PIC_LEN-hnt) len=ANALOGTV_PIC_LEN-hnt; |
---|
1721 | for (i=0; i<len; i++) { |
---|
1722 | double sig=signal[hnt]; |
---|
1723 | |
---|
1724 | sig += cur*maxampl; |
---|
1725 | cur += frand(5.0)-5.0; |
---|
1726 | maxampl = maxampl*0.9; |
---|
1727 | |
---|
1728 | signal[hnt]=sig; |
---|
1729 | hnt++; |
---|
1730 | } |
---|
1731 | } |
---|
1732 | } |
---|
1733 | #endif |
---|
1734 | |
---|
1735 | |
---|
1736 | void analogtv_init_signal(analogtv *it, double noiselevel) |
---|
1737 | { |
---|
1738 | double *ps=it->rx_signal; |
---|
1739 | double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN; |
---|
1740 | double *p=ps; |
---|
1741 | unsigned int fastrnd=random(); |
---|
1742 | double nm1=0.0,nm2=0.0; |
---|
1743 | double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff; |
---|
1744 | |
---|
1745 | while (p != pe) { |
---|
1746 | nm2=nm1; |
---|
1747 | nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul; |
---|
1748 | *p++ = nm1*nm2; |
---|
1749 | fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu; |
---|
1750 | } |
---|
1751 | |
---|
1752 | it->rx_signal_level = noiselevel; |
---|
1753 | } |
---|
1754 | |
---|
1755 | void |
---|
1756 | analogtv_reception_update(analogtv_reception *rec) |
---|
1757 | { |
---|
1758 | int i; |
---|
1759 | |
---|
1760 | if (rec->multipath > 0.0) { |
---|
1761 | for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) { |
---|
1762 | rec->ghostfir2[i] += |
---|
1763 | -(rec->ghostfir2[i]/16.0) + rec->multipath * (frand(0.02)-0.01); |
---|
1764 | } |
---|
1765 | if (random()%20==0) { |
---|
1766 | rec->ghostfir2[random()%(ANALOGTV_GHOSTFIR_LEN)] |
---|
1767 | = rec->multipath * (frand(0.08)-0.04); |
---|
1768 | } |
---|
1769 | for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) { |
---|
1770 | rec->ghostfir[i] = 0.8*rec->ghostfir[i] + 0.2*rec->ghostfir2[i]; |
---|
1771 | } |
---|
1772 | |
---|
1773 | if (0) { |
---|
1774 | rec->hfloss2 += -(rec->hfloss2/16.0) + rec->multipath * (frand(0.08)-0.04); |
---|
1775 | rec->hfloss = 0.5*rec->hfloss + 0.5*rec->hfloss2; |
---|
1776 | } |
---|
1777 | |
---|
1778 | } else { |
---|
1779 | for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) { |
---|
1780 | rec->ghostfir[i] = (i>=ANALOGTV_GHOSTFIR_LEN/2) ? ((i&1) ? +0.04 : -0.08) /ANALOGTV_GHOSTFIR_LEN |
---|
1781 | : 0.0; |
---|
1782 | } |
---|
1783 | } |
---|
1784 | } |
---|
1785 | |
---|
1786 | |
---|
1787 | void |
---|
1788 | analogtv_make_font(Display *dpy, Window window, analogtv_font *f, |
---|
1789 | int w, int h, char *fontname) |
---|
1790 | { |
---|
1791 | int i; |
---|
1792 | XFontStruct *font; |
---|
1793 | Pixmap text_pm; |
---|
1794 | GC gc; |
---|
1795 | XGCValues gcv; |
---|
1796 | XWindowAttributes xgwa; |
---|
1797 | |
---|
1798 | f->char_w = w; |
---|
1799 | f->char_h = h; |
---|
1800 | |
---|
1801 | XGetWindowAttributes (dpy, window, &xgwa); |
---|
1802 | |
---|
1803 | if (fontname) { |
---|
1804 | |
---|
1805 | font = XLoadQueryFont (dpy, fontname); |
---|
1806 | if (!font) { |
---|
1807 | fprintf(stderr, "analogtv: can't load font %s\n", fontname); |
---|
1808 | abort(); |
---|
1809 | } |
---|
1810 | |
---|
1811 | text_pm=XCreatePixmap(dpy, window, 128*f->char_w, f->char_h, xgwa.depth); |
---|
1812 | |
---|
1813 | memset(&gcv, 0, sizeof(gcv)); |
---|
1814 | gcv.foreground=1; |
---|
1815 | gcv.background=0; |
---|
1816 | gcv.font=font->fid; |
---|
1817 | gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv); |
---|
1818 | |
---|
1819 | XSetForeground(dpy, gc, 0); |
---|
1820 | XFillRectangle(dpy, text_pm, gc, 0, 0, 128*f->char_w, f->char_h); |
---|
1821 | XSetForeground(dpy, gc, 1); |
---|
1822 | /* Just ASCII */ |
---|
1823 | for (i=0; i<128; i++) { |
---|
1824 | char c=i; |
---|
1825 | int x=f->char_w*i+1; |
---|
1826 | int y=f->char_h*8/10; |
---|
1827 | XDrawString(dpy, text_pm, gc, x, y, &c, 1); |
---|
1828 | } |
---|
1829 | f->text_im = XGetImage(dpy, text_pm, 0, 0, 128*f->char_w, f->char_h, |
---|
1830 | ~0L, ZPixmap); |
---|
1831 | XFreeGC(dpy, gc); |
---|
1832 | XFreePixmap(dpy, text_pm); |
---|
1833 | } else { |
---|
1834 | f->text_im = XCreateImage(dpy, xgwa.visual, xgwa.depth, |
---|
1835 | ZPixmap, 0, 0, |
---|
1836 | 128*f->char_w, f->char_h, 8, 0); |
---|
1837 | f->text_im->data = (char *)calloc(f->text_im->height, |
---|
1838 | f->text_im->bytes_per_line); |
---|
1839 | |
---|
1840 | } |
---|
1841 | f->x_mult=4; |
---|
1842 | f->y_mult=2; |
---|
1843 | } |
---|
1844 | |
---|
1845 | int |
---|
1846 | analogtv_font_pixel(analogtv_font *f, int c, int x, int y) |
---|
1847 | { |
---|
1848 | if (x<0 || x>=f->char_w) return 0; |
---|
1849 | if (y<0 || y>=f->char_h) return 0; |
---|
1850 | if (c<0 || c>=128) return 0; |
---|
1851 | return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0; |
---|
1852 | } |
---|
1853 | |
---|
1854 | void |
---|
1855 | analogtv_font_set_pixel(analogtv_font *f, int c, int x, int y, int value) |
---|
1856 | { |
---|
1857 | if (x<0 || x>=f->char_w) return; |
---|
1858 | if (y<0 || y>=f->char_h) return; |
---|
1859 | if (c<0 || c>=128) return; |
---|
1860 | |
---|
1861 | XPutPixel(f->text_im, c*f->char_w + x, y, value); |
---|
1862 | } |
---|
1863 | |
---|
1864 | void |
---|
1865 | analogtv_font_set_char(analogtv_font *f, int c, char *s) |
---|
1866 | { |
---|
1867 | int value,x,y; |
---|
1868 | |
---|
1869 | if (c<0 || c>=128) return; |
---|
1870 | |
---|
1871 | for (y=0; y<f->char_h; y++) { |
---|
1872 | for (x=0; x<f->char_w; x++) { |
---|
1873 | if (!*s) return; |
---|
1874 | value=(*s==' ') ? 0 : 1; |
---|
1875 | analogtv_font_set_pixel(f, c, x, y, value); |
---|
1876 | s++; |
---|
1877 | } |
---|
1878 | } |
---|
1879 | } |
---|
1880 | |
---|
1881 | void |
---|
1882 | analogtv_lcp_to_ntsc(double luma, double chroma, double phase, int ntsc[4]) |
---|
1883 | { |
---|
1884 | int i; |
---|
1885 | for (i=0; i<4; i++) { |
---|
1886 | double w=90.0*i + phase; |
---|
1887 | double val=luma + chroma * (cos(3.1415926/180.0*w)); |
---|
1888 | if (val<0.0) val=0.0; |
---|
1889 | if (val>127.0) val=127.0; |
---|
1890 | ntsc[i]=(int)val; |
---|
1891 | } |
---|
1892 | } |
---|
1893 | |
---|
1894 | void |
---|
1895 | analogtv_draw_solid(analogtv_input *input, |
---|
1896 | int left, int right, int top, int bot, |
---|
1897 | int ntsc[4]) |
---|
1898 | { |
---|
1899 | int x,y; |
---|
1900 | |
---|
1901 | if (right-left<4) right=left+4; |
---|
1902 | if (bot-top<1) bot=top+1; |
---|
1903 | |
---|
1904 | for (y=top; y<bot; y++) { |
---|
1905 | for (x=left; x<right; x++) { |
---|
1906 | input->signal[y][x] = ntsc[x&3]; |
---|
1907 | } |
---|
1908 | } |
---|
1909 | } |
---|
1910 | |
---|
1911 | |
---|
1912 | void |
---|
1913 | analogtv_draw_solid_rel_lcp(analogtv_input *input, |
---|
1914 | double left, double right, double top, double bot, |
---|
1915 | double luma, double chroma, double phase) |
---|
1916 | { |
---|
1917 | int ntsc[4]; |
---|
1918 | |
---|
1919 | int topi=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*top); |
---|
1920 | int boti=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*bot); |
---|
1921 | int lefti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*left); |
---|
1922 | int righti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*right); |
---|
1923 | |
---|
1924 | analogtv_lcp_to_ntsc(luma, chroma, phase, ntsc); |
---|
1925 | analogtv_draw_solid(input, lefti, righti, topi, boti, ntsc); |
---|
1926 | } |
---|
1927 | |
---|
1928 | |
---|
1929 | void |
---|
1930 | analogtv_draw_char(analogtv_input *input, analogtv_font *f, |
---|
1931 | int c, int x, int y, int ntsc[4]) |
---|
1932 | { |
---|
1933 | int yc,xc,ys,xs,pix; |
---|
1934 | |
---|
1935 | for (yc=0; yc<f->char_h; yc++) { |
---|
1936 | for (ys=y + yc*f->y_mult; ys<y + (yc+1)*f->y_mult; ys++) { |
---|
1937 | if (ys<0 || ys>=ANALOGTV_V) continue; |
---|
1938 | |
---|
1939 | for (xc=0; xc<f->char_w; xc++) { |
---|
1940 | pix=analogtv_font_pixel(f, c, xc, yc); |
---|
1941 | |
---|
1942 | for (xs=x + xc*f->x_mult; xs<x + (xc+1)*f->x_mult; xs++) { |
---|
1943 | if (xs<0 || xs>=ANALOGTV_H) continue; |
---|
1944 | if (pix) { |
---|
1945 | input->signal[ys][xs] = ntsc[xs&3]; |
---|
1946 | } |
---|
1947 | } |
---|
1948 | } |
---|
1949 | } |
---|
1950 | } |
---|
1951 | } |
---|
1952 | |
---|
1953 | void |
---|
1954 | analogtv_draw_string(analogtv_input *input, analogtv_font *f, |
---|
1955 | char *s, int x, int y, int ntsc[4]) |
---|
1956 | { |
---|
1957 | while (*s) { |
---|
1958 | analogtv_draw_char(input, f, *s, x, y, ntsc); |
---|
1959 | x += f->char_w * 4; |
---|
1960 | s++; |
---|
1961 | } |
---|
1962 | } |
---|
1963 | |
---|
1964 | void |
---|
1965 | analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f, |
---|
1966 | char *s, int x, int y, int ntsc[4]) |
---|
1967 | { |
---|
1968 | int width=strlen(s) * f->char_w * 4; |
---|
1969 | x -= width/2; |
---|
1970 | |
---|
1971 | analogtv_draw_string(input, f, s, x, y, ntsc); |
---|
1972 | } |
---|
1973 | |
---|
1974 | |
---|
1975 | static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1976 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1977 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1978 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, |
---|
1979 | 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1980 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1981 | 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0, |
---|
1982 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
---|
1983 | |
---|
1984 | /* |
---|
1985 | Much of this function was adapted from logo.c |
---|
1986 | */ |
---|
1987 | void |
---|
1988 | analogtv_draw_xpm(analogtv *tv, analogtv_input *input, |
---|
1989 | const char * const *xpm, int left, int top) |
---|
1990 | { |
---|
1991 | int xpmw,xpmh; |
---|
1992 | int x,y,tvx,tvy,i; |
---|
1993 | int rawy,rawi,rawq; |
---|
1994 | int ncolors, nbytes; |
---|
1995 | char dummyc; |
---|
1996 | struct { |
---|
1997 | int r; int g; int b; |
---|
1998 | } cmap[256]; |
---|
1999 | |
---|
2000 | |
---|
2001 | if (4 != sscanf ((const char *) *xpm, |
---|
2002 | "%d %d %d %d %c", |
---|
2003 | &xpmw, &xpmh, &ncolors, &nbytes, &dummyc)) |
---|
2004 | abort(); |
---|
2005 | if (ncolors < 1 || ncolors > 255) |
---|
2006 | abort(); |
---|
2007 | if (nbytes != 1) /* a serious limitation */ |
---|
2008 | abort(); |
---|
2009 | xpm++; |
---|
2010 | |
---|
2011 | for (i = 0; i < ncolors; i++) { |
---|
2012 | const char *line = *xpm; |
---|
2013 | int colori = ((unsigned char)*line++)&0xff; |
---|
2014 | while (*line) |
---|
2015 | { |
---|
2016 | int r, g, b; |
---|
2017 | char which; |
---|
2018 | while (*line == ' ' || *line == '\t') |
---|
2019 | line++; |
---|
2020 | which = *line++; |
---|
2021 | if (which != 'c' && which != 'm') |
---|
2022 | abort(); |
---|
2023 | while (*line == ' ' || *line == '\t') |
---|
2024 | line++; |
---|
2025 | if (!strncasecmp(line, "None", 4)) |
---|
2026 | { |
---|
2027 | r = g = b = -1; |
---|
2028 | line += 4; |
---|
2029 | } |
---|
2030 | else |
---|
2031 | { |
---|
2032 | if (*line == '#') |
---|
2033 | line++; |
---|
2034 | r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]]; |
---|
2035 | line += 2; |
---|
2036 | g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]]; |
---|
2037 | line += 2; |
---|
2038 | b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]]; |
---|
2039 | line += 2; |
---|
2040 | } |
---|
2041 | |
---|
2042 | if (which == 'c') |
---|
2043 | { |
---|
2044 | cmap[colori].r = r; |
---|
2045 | cmap[colori].g = g; |
---|
2046 | cmap[colori].b = b; |
---|
2047 | } |
---|
2048 | } |
---|
2049 | |
---|
2050 | xpm++; |
---|
2051 | } |
---|
2052 | |
---|
2053 | for (y=0; y<xpmh; y++) { |
---|
2054 | const char *line = *xpm++; |
---|
2055 | tvy=y+top; |
---|
2056 | if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue; |
---|
2057 | |
---|
2058 | for (x=0; x<xpmw; x++) { |
---|
2059 | int cbyte=((unsigned char)line[x])&0xff; |
---|
2060 | int ntsc[4]; |
---|
2061 | tvx=x*4+left; |
---|
2062 | if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue; |
---|
2063 | |
---|
2064 | rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64; |
---|
2065 | rawi=(10*cmap[cbyte].r - 4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64; |
---|
2066 | rawq=( 3*cmap[cbyte].r - 8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64; |
---|
2067 | |
---|
2068 | ntsc[0]=rawy+rawq; |
---|
2069 | ntsc[1]=rawy-rawi; |
---|
2070 | ntsc[2]=rawy-rawq; |
---|
2071 | ntsc[3]=rawy+rawi; |
---|
2072 | |
---|
2073 | for (i=0; i<4; i++) { |
---|
2074 | if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL; |
---|
2075 | if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL; |
---|
2076 | } |
---|
2077 | |
---|
2078 | input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3]; |
---|
2079 | input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3]; |
---|
2080 | input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3]; |
---|
2081 | input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3]; |
---|
2082 | } |
---|
2083 | } |
---|
2084 | } |
---|
2085 | |
---|
2086 | extern XtAppContext app; |
---|
2087 | |
---|
2088 | int |
---|
2089 | analogtv_handle_events (analogtv *it) |
---|
2090 | { |
---|
2091 | XSync(it->dpy, False); |
---|
2092 | if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput)) |
---|
2093 | XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput); |
---|
2094 | |
---|
2095 | while (XPending (it->dpy)) |
---|
2096 | { |
---|
2097 | XEvent event; |
---|
2098 | XNextEvent (it->dpy, &event); |
---|
2099 | switch (event.xany.type) |
---|
2100 | { |
---|
2101 | case ButtonPress: |
---|
2102 | return 1; |
---|
2103 | |
---|
2104 | case KeyPress: |
---|
2105 | { |
---|
2106 | KeySym keysym; |
---|
2107 | char c = 0; |
---|
2108 | XLookupString (&event.xkey, &c, 1, &keysym, 0); |
---|
2109 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n') |
---|
2110 | return 1; |
---|
2111 | } |
---|
2112 | break; |
---|
2113 | |
---|
2114 | /* I don't seem to get an event when clicking the "full |
---|
2115 | screen" window manager icon, at least when using |
---|
2116 | metacity. Thus, it doesn't change the video size. Is this |
---|
2117 | some separate WM_* message I have to deal with? |
---|
2118 | */ |
---|
2119 | case ConfigureNotify: |
---|
2120 | if (event.xconfigure.width != it->xgwa.width || |
---|
2121 | event.xconfigure.height != it->xgwa.height) |
---|
2122 | analogtv_reconfigure(it); |
---|
2123 | break; |
---|
2124 | |
---|
2125 | case Expose: |
---|
2126 | case GraphicsExpose: |
---|
2127 | it->need_clear=1; |
---|
2128 | break; |
---|
2129 | |
---|
2130 | default: |
---|
2131 | break; |
---|
2132 | } |
---|
2133 | if (it->event_handler) { |
---|
2134 | (*it->event_handler) (it->dpy, &event); |
---|
2135 | } |
---|
2136 | } |
---|
2137 | return 0; |
---|
2138 | } |
---|
2139 | |
---|