-/* analogtv, Copyright (c) 2003, 2004 Trevor Blackwell <tlb@tlb.org>
+/* analogtv, Copyright (c) 2003-2018 Trevor Blackwell <tlb@tlb.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
#include "resources.h"
#include "analogtv.h"
#include "yarandom.h"
-#include "grabscreen.h"
+#include "grabclient.h"
#include "visual.h"
+#include "xft.h"
+#include "font-retry.h"
+#include "ximage-loader.h"
/* #define DEBUG 1 */
/* If the window is very small, don't let the image we draw get lower
than the actual TV resolution (266x200.)
- If the aspect ratio of the window is close to a 4:3 or 16:9 ratio,
+ If the aspect ratio of the window is close to a 4:3 or 16:9 ratio --
+ or if it is a completely weird aspect ratio --
then scale the image to exactly fill the window.
Otherwise, center the image either horizontally or vertically,
float percent = 0.15;
float min_ratio = 4.0 / 3.0 * (1 - percent);
float max_ratio = 16.0 / 9.0 * (1 + percent);
+ float crazy_min_ratio = 10;
+ float crazy_max_ratio = 1/crazy_min_ratio;
float ratio;
float height_snap=0.025;
wlim = it->xgwa.width;
ratio = wlim / (float) hlim;
-#ifdef HAVE_MOBILE
+#if defined(HAVE_MOBILE) || defined(NO_CONSTRAIN_RATIO)
/* Fill the whole iPhone screen, even though that distorts the image. */
min_ratio = 0;
max_ratio = 10;
# endif
}
+ if (ratio < crazy_min_ratio || ratio > crazy_max_ratio)
+ {
+ if (ratio < crazy_min_ratio)
+ hlim = it->xgwa.height;
+ else
+ wlim = it->xgwa.width;
+# ifdef DEBUG
+ fprintf (stderr,
+ "size: aspect: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
+ wlim, hlim, it->xgwa.width, it->xgwa.height,
+ min_ratio, ratio, max_ratio);
+# endif
+ }
+
height_diff = ((hlim + ANALOGTV_VISLINES/2) % ANALOGTV_VISLINES) - ANALOGTV_VISLINES/2;
if (height_diff != 0 && abs(height_diff) < hlim * height_snap)
"background", "Background");
it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (it->dpy, it->gc, False);
+# endif
XSetWindowBackground(it->dpy, it->window, gcv.background);
XClearWindow(dpy,window);
cmap_again:
if (it->use_cmap && !it->n_colors) {
- if (it->n_colors) {
- XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
- it->n_colors=0;
- }
-
{
int yli,qli,ili;
for (yli=0; yli<y_levels; yli++) {
if (do_cb) {
/* 9 cycles of colorburst */
- for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) {
+ for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36*ANALOGTV_SCALE; i+=4*ANALOGTV_SCALE) {
sig[i+1] += ANALOGTV_CB_LEVEL;
sig[i+3] -= ANALOGTV_CB_LEVEL;
}
float cbfc=1.0f/128.0f;
/* sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;*/
- for (i=-32; i<32; i++) {
+ for (i=-32*ANALOGTV_SCALE; i<32*ANALOGTV_SCALE; i++) {
lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
sp = it->rx_signal + lineno*ANALOGTV_H;
filt=0.0f;
- for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
+ for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/(16*ANALOGTV_SCALE)) {
filt += sp[j];
}
filt *= it->agclevel;
for (lineno=0; lineno<ANALOGTV_V; lineno++) {
- if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
+ if (lineno>5*ANALOGTV_SCALE && lineno<ANALOGTV_V-3*ANALOGTV_SCALE) { /* ignore vsync interval */
unsigned lineno2 = (lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V;
if (!lineno2) lineno2 = ANALOGTV_V;
sp = it->rx_signal + lineno2*ANALOGTV_H + cur_hsync;
- for (i=-8; i<8; i++) {
+ for (i=-8*ANALOGTV_SCALE; i<8*ANALOGTV_SCALE; i++) {
osc = (float)(ANALOGTV_H+i)/(float)ANALOGTV_H;
filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
cycles.
*/
- if (lineno>15) {
+ if (lineno>15*ANALOGTV_SCALE) {
sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
- for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
+ for (i=ANALOGTV_CB_START+8*ANALOGTV_SCALE; i<ANALOGTV_CB_START+(36-8)*ANALOGTV_SCALE; i++) {
it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0f-cbfc) +
sp[i]*it->agclevel*cbfc;
}
analogtv_setup_levels(it, it->puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
/* calculate tint once per frame */
- it->tint_i = -cos((103 + it->tint_control)*3.1415926/180);
- it->tint_q = sin((103 + it->tint_control)*3.1415926/180);
+ /* Christopher Mosher argues that this should use 33 degress instead of
+ 103 degrees, and then TVTint should default to 0 in analogtv.h and
+ all relevant XML files. But that makes all the colors go really green
+ and saturated, so apparently that's not right. -- jwz, Nov 2020.
+ */
+ it->tint_i = -cos((103 + it->tint_control)*M_PI/180);
+ it->tint_q = sin((103 + it->tint_control)*M_PI/180);
for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
int slineno, ytop, ybot;
}
analogtv_input *
-analogtv_input_allocate()
+analogtv_input_allocate(void)
{
analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input));
This takes a screen image and encodes it as a video camera would,
including all the bandlimiting and YIQ modulation.
This isn't especially tuned for speed.
+
+ xoff, yoff: top left corner of rendered image, in window pixels.
+ w, h: scaled size of rendered image, in window pixels.
+ mask: BlackPixel means don't render (it's not full alpha)
*/
int
-analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im)
+analogtv_load_ximage(analogtv *it, analogtv_input *input,
+ XImage *pic_im, XImage *mask_im,
+ int xoff, int yoff, int target_w, int target_h)
{
int i,x,y;
int img_w,img_h;
int fqx[4],fqy[4];
XColor col1[ANALOGTV_PIC_LEN];
XColor col2[ANALOGTV_PIC_LEN];
+ char mask[ANALOGTV_PIC_LEN];
int multiq[ANALOGTV_PIC_LEN+4];
- int y_overscan=5; /* overscan this much top and bottom */
+ unsigned long black = 0; /* not BlackPixelOfScreen (it->xgwa.screen); */
+
+ int x_length=ANALOGTV_PIC_LEN;
+ int y_overscan=5*ANALOGTV_SCALE; /* overscan this much top and bottom */
int y_scanlength=ANALOGTV_VISLINES+2*y_overscan;
- img_w=pic_im->width;
- img_h=pic_im->height;
+ if (target_w > 0) x_length = x_length * target_w / it->xgwa.width;
+ if (target_h > 0) y_scanlength = y_scanlength * target_h / it->xgwa.height;
+
+ img_w = pic_im->width;
+ img_h = pic_im->height;
- for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
+ xoff = ANALOGTV_PIC_LEN * xoff / it->xgwa.width;
+ yoff = ANALOGTV_VISLINES * yoff / it->xgwa.height;
+
+ for (i=0; i<x_length+4; i++) {
double phase=90.0-90.0*i;
double ampl=1.0;
- multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl);
+ multiq[i]=(int)(-cos(M_PI/180.0*(phase-303)) * 4096.0 * ampl);
}
for (y=0; y<y_scanlength; y++) {
int picy1=(y*img_h)/y_scanlength;
int picy2=(y*img_h + y_scanlength/2)/y_scanlength;
- for (x=0; x<ANALOGTV_PIC_LEN; x++) {
- int picx=(x*img_w)/ANALOGTV_PIC_LEN;
+ for (x=0; x<x_length; x++) {
+ int picx=(x*img_w)/x_length;
col1[x].pixel=XGetPixel(pic_im, picx, picy1);
col2[x].pixel=XGetPixel(pic_im, picx, picy2);
+ if (mask_im)
+ mask[x] = (XGetPixel(mask_im, picx, picy1) != black);
+ else
+ mask[x] = 1;
}
- XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN);
- XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN);
-
+ XQueryColors(it->dpy, it->colormap, col1, x_length);
+ XQueryColors(it->dpy, it->colormap, col2, x_length);
for (i=0; i<7; i++) fyx[i]=fyy[i]=0;
for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0;
- for (x=0; x<ANALOGTV_PIC_LEN; x++) {
+ for (x=0; x<x_length; x++) {
int rawy,rawi,rawq;
int filty,filti,filtq;
int composite;
+
+ if (!mask[x]) continue;
+
/* Compute YIQ as:
y=0.30 r + 0.59 g + 0.11 b
i=0.60 r - 0.28 g - 0.32 b
composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL;
if (composite>125) composite=125;
if (composite<0) composite=0;
- input->signal[y-y_overscan+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite;
+
+ input->signal[y-y_overscan+ANALOGTV_TOP+yoff][x+ANALOGTV_PIC_START+xoff] = composite;
}
}
}
-/* jwz: since MacOS doesn't have "6x10", I dumped this font to an XBM...
+/* jwz: since MacOS doesn't have "6x10", I dumped this font to a PNG...
*/
-#include "images/6x10font.xbm"
+#include "images/gen/6x10font_png.h"
void
analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
int w, int h, char *fontname)
{
int i;
- XFontStruct *font;
- Pixmap text_pm;
GC gc;
XGCValues gcv;
XWindowAttributes xgwa;
if (fontname && !strcmp (fontname, "6x10")) {
- text_pm = XCreatePixmapFromBitmapData (dpy, window,
- (char *) font6x10_bits,
- font6x10_width,
- font6x10_height,
- 1, 0, 1);
- f->text_im = XGetImage(dpy, text_pm, 0, 0, font6x10_width, font6x10_height,
- 1, XYPixmap);
- XFreePixmap(dpy, text_pm);
+ int pix_w, pix_h;
+ XWindowAttributes xgwa;
+ Pixmap m = 0;
+ Pixmap p = image_data_to_pixmap (dpy, window,
+ _6x10font_png, sizeof(_6x10font_png),
+ &pix_w, &pix_h, &m);
+ XImage *im = XGetImage (dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap);
+ XImage *mm = XGetImage (dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap);
+ unsigned long black = BlackPixelOfScreen (DefaultScreenOfDisplay (dpy));
+ int x, y;
+
+ XFreePixmap (dpy, p);
+ XFreePixmap (dpy, m);
+ if (pix_w != 256*7) abort();
+ if (pix_h != 10) abort();
+
+ XGetWindowAttributes (dpy, window, &xgwa);
+ f->text_im = XCreateImage (dpy, xgwa.visual, 1, XYBitmap, 0, 0,
+ pix_w, pix_h, 8, 0);
+ f->text_im->data = malloc (f->text_im->bytes_per_line * f->text_im->height);
+
+ /* Convert deep image to 1 bit */
+ for (y = 0; y < pix_h; y++)
+ for (x = 0; x < pix_w; x++)
+ XPutPixel (f->text_im, x, y,
+ (XGetPixel (mm, x, y)
+ ? XGetPixel (im, x, y) == black
+ : 0));
+ XDestroyImage (im);
+ XDestroyImage (mm);
} else if (fontname) {
- font = XLoadQueryFont (dpy, fontname);
+ XftFont *font;
+ XftColor xft_fg;
+ XftDraw *xftdraw;
+ Pixmap text_pm;
+ XImage *xim;
+ int x, y;
+
+ font = load_xft_font_retry (dpy, screen_number (xgwa.screen), fontname);
if (!font) {
fprintf(stderr, "analogtv: can't load font %s\n", fontname);
abort();
}
- text_pm=XCreatePixmap(dpy, window, 256*f->char_w, f->char_h, 1);
+ text_pm=XCreatePixmap(dpy, window, 256*f->char_w, f->char_h, xgwa.depth);
memset(&gcv, 0, sizeof(gcv));
gcv.foreground=1;
gcv.background=0;
- gcv.font=font->fid;
- gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
+ gc=XCreateGC(dpy, text_pm, GCBackground|GCForeground, &gcv);
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (dpy, gc, False);
+# endif
XSetForeground(dpy, gc, 0);
XFillRectangle(dpy, text_pm, gc, 0, 0, 256*f->char_w, f->char_h);
- XSetForeground(dpy, gc, 1);
+
+ xftdraw = XftDrawCreate (dpy, text_pm, xgwa.visual, xgwa.colormap);
+ xft_fg.pixel = ~0L;
+ xft_fg.color.red = xft_fg.color.green = xft_fg.color.blue = ~0L;
+
for (i=0; i<256; i++) {
char c=i;
int x=f->char_w*i+1;
int y=f->char_h*8/10;
- XDrawString(dpy, text_pm, gc, x, y, &c, 1);
+ XftDrawStringUtf8 (xftdraw, &xft_fg, font, x, y, (FcChar8 *) &c, 1);
}
- f->text_im = XGetImage(dpy, text_pm, 0, 0, 256*f->char_w, f->char_h,
- 1, XYPixmap);
+ xim = XGetImage(dpy, text_pm, 0, 0, 256*f->char_w, f->char_h,
+ xgwa.depth, ZPixmap);
+ f->text_im = XCreateImage(dpy, xgwa.visual, 1, XYPixmap, 0, 0,
+ 256*f->char_w, f->char_h, 8, 0);
+ f->text_im->data = (char *)calloc(f->text_im->height,
+ f->text_im->bytes_per_line);
+ for (y = 0; y < xim->height; y++)
+ for (x = 0; x < xim->width; x++)
+ XPutPixel (f->text_im, x, y, XGetPixel (xim, x, y) ? 1 : 0);
+
+ XDestroyImage (xim);
+ xim = 0;
+
# if 0
XWriteBitmapFile(dpy, "/tmp/tvfont.xbm", text_pm,
256*f->char_w, f->char_h, -1, -1);
# endif
XFreeGC(dpy, gc);
XFreePixmap(dpy, text_pm);
+ XftDrawDestroy (xftdraw);
+
} else {
f->text_im = XCreateImage(dpy, xgwa.visual, 1, XYPixmap, 0, 0,
256*f->char_w, f->char_h, 8, 0);
f->text_im->bytes_per_line);
}
- f->x_mult=4;
- f->y_mult=2;
+ f->x_mult=4 * ANALOGTV_SCALE;
+ f->y_mult=2 * ANALOGTV_SCALE;
}
int
int i;
for (i=0; i<4; i++) {
double w=90.0*i + phase;
- double val=luma + chroma * (cos(3.1415926/180.0*w));
+ double val=luma + chroma * (cos(M_PI/180.0*w));
if (val<0.0) val=0.0;
if (val>127.0) val=127.0;
ntsc[i]=(int)val;
{
while (*s) {
analogtv_draw_char(input, f, *s, x, y, ntsc);
- x += f->char_w * 4;
+ x += f->char_w * f->x_mult;
s++;
}
}
analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f,
char *s, int x, int y, int ntsc[4])
{
- int width=strlen(s) * f->char_w * 4;
+ int width=strlen(s) * f->char_w * f->x_mult;
x -= width/2;
analogtv_draw_string(input, f, s, x, y, ntsc);
}
-
-
-static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
- 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-/*
- Much of this function was adapted from logo.c
- */
-void
-analogtv_draw_xpm(analogtv *tv, analogtv_input *input,
- const char * const *xpm, int left, int top)
-{
- int xpmw,xpmh;
- int x,y,tvx,tvy,i;
- int rawy,rawi,rawq;
- int ncolors, nbytes;
- char dummyc;
- struct {
- int r; int g; int b;
- } cmap[256];
-
-
- if (4 != sscanf ((const char *) *xpm,
- "%d %d %d %d %c",
- &xpmw, &xpmh, &ncolors, &nbytes, &dummyc))
- abort();
- if (ncolors < 1 || ncolors > 255)
- abort();
- if (nbytes != 1) /* a serious limitation */
- abort();
- xpm++;
-
- for (i = 0; i < ncolors; i++) {
- const char *line = *xpm;
- int colori = ((unsigned char)*line++)&0xff;
- while (*line)
- {
- int r, g, b;
- char which;
- while (*line == ' ' || *line == '\t')
- line++;
- which = *line++;
- if (which != 'c' && which != 'm')
- abort();
- while (*line == ' ' || *line == '\t')
- line++;
- if (!strncasecmp(line, "None", 4))
- {
- r = g = b = -1;
- line += 4;
- }
- else
- {
- if (*line == '#')
- line++;
- r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
- line += 2;
- g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
- line += 2;
- b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
- line += 2;
- }
-
- if (which == 'c')
- {
- cmap[colori].r = r;
- cmap[colori].g = g;
- cmap[colori].b = b;
- }
- }
-
- xpm++;
- }
-
- for (y=0; y<xpmh; y++) {
- const char *line = *xpm++;
- tvy=y+top;
- if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue;
-
- for (x=0; x<xpmw; x++) {
- int cbyte=((unsigned char)line[x])&0xff;
- int ntsc[4];
- tvx=x*4+left;
- if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue;
-
- rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64;
- rawi=(10*cmap[cbyte].r - 4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64;
- rawq=( 3*cmap[cbyte].r - 8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64;
-
- ntsc[0]=rawy+rawq;
- ntsc[1]=rawy-rawi;
- ntsc[2]=rawy-rawq;
- ntsc[3]=rawy+rawi;
-
- for (i=0; i<4; i++) {
- if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL;
- if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL;
- }
-
- input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3];
- input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3];
- input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3];
- input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3];
- }
- }
-}