1 /* photopile, Copyright (c) 2008 Jens Kilian <jjk@acm.org>
2 * Based on carousel, Copyright (c) 2005-2008 Jamie Zawinski <jwz@jwz.org>
3 * Loads a sequence of images and shuffles them into a pile.
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation. No representations are made about the suitability of this
10 * software for any purpose. It is provided "as is" without express or
14 #define DEF_FONT "-*-helvetica-bold-r-normal-*-240-*"
15 #define DEFAULTS "*count: 7 \n" \
17 "*wireframe: False \n" \
18 "*showFPS: False \n" \
19 "*fpsSolid: True \n" \
21 "*font: " DEF_FONT "\n" \
22 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
23 "*grabDesktopImages: False \n" \
24 "*chooseRandomImages: True \n"
26 # define refresh_photopile 0
27 # define release_photopile 0
28 # define photopile_handle_event 0
31 #define countof(x) (sizeof((x))/sizeof((*x)))
34 # include <X11/Intrinsic.h> /* for XrmDatabase in -debug mode */
38 #include "xlockmore.h"
39 #include "grab-ximage.h"
44 # define DEF_SCALE "0.4"
45 # define DEF_MAX_TILT "50"
46 # define DEF_SPEED "1.0"
47 # define DEF_DURATION "5"
48 # define DEF_TITLES "False"
49 # define DEF_MIPMAP "True"
50 # define DEF_DEBUG "False"
52 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
55 GLfloat x, y; /* position on screen */
56 GLfloat angle; /* rotation angle */
61 Bool loaded_p; /* true if image can be drawn */
63 char *title; /* the filename of this image */
64 int w, h; /* size in pixels of the image */
65 int tw, th; /* size in pixels of the texture */
66 XRectangle geom; /* where in the image the bits are */
68 position pos[4]; /* control points for calculating position */
70 GLuint texid; /* GL texture ID */
75 typedef enum { EARLY, IN, NORMAL, LOADING, SHUFFLE } fade_mode;
76 static int fade_ticks = 60;
80 GLXContext *glx_context;
82 image *frames; /* pointer to array of images */
83 int nframe; /* image being (resp. next to be) loaded */
85 texture_font_data *texfont;
86 int loading_sw, loading_sh;
88 time_t last_time, now;
95 static photopile_state *sss = NULL;
98 /* Command-line arguments
100 static GLfloat scale; /* Scale factor for loading images. */
101 static GLfloat max_tilt; /* Maximum angle from vertical. */
102 static GLfloat speed; /* Animation speed scale factor. */
103 static int duration; /* Reload images after this long. */
104 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
105 static Bool titles_p; /* Display image titles. */
106 static Bool debug_p; /* Be loud and do weird things. */
109 static XrmOptionDescRec opts[] = {
110 {"-scale", ".scale", XrmoptionSepArg, 0 },
111 {"-maxTilt", ".maxTilt", XrmoptionSepArg, 0 },
112 {"-titles", ".titles", XrmoptionNoArg, "True" },
113 {"-no-titles", ".titles", XrmoptionNoArg, "False" },
114 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
115 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
116 {"-duration", ".duration", XrmoptionSepArg, 0 },
117 {"-debug", ".debug", XrmoptionNoArg, "True" },
118 {"-font", ".font", XrmoptionSepArg, 0 },
119 {"-speed", ".speed", XrmoptionSepArg, 0 },
122 static argtype vars[] = {
123 { &scale, "scale", "Scale", DEF_SCALE, t_Float},
124 { &max_tilt, "maxTilt", "MaxTilt", DEF_MAX_TILT, t_Float},
125 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
126 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
127 { &titles_p, "titles", "Titles", DEF_TITLES, t_Bool},
128 { &speed, "speed", "Speed", DEF_SPEED, t_Float},
129 { &duration, "duration", "Duration", DEF_DURATION, t_Int},
132 ENTRYPOINT ModeSpecOpt photopile_opts = {countof(opts), opts, countof(vars), vars, NULL};
135 /* Functions to interpolate between image positions.
138 add_pos(position p, position q)
147 scale_pos(GLfloat t, position p)
156 linear_combination(GLfloat t, position p, position q)
158 return add_pos(scale_pos(1.0 - t, p), scale_pos(t, q));
162 interpolate(GLfloat t, position p[4])
164 /* de Casteljau's algorithm, 4 control points */
165 position p10 = linear_combination(t, p[0], p[1]);
166 position p11 = linear_combination(t, p[1], p[2]);
167 position p12 = linear_combination(t, p[2], p[3]);
169 position p20 = linear_combination(t, p10, p11);
170 position p21 = linear_combination(t, p11, p12);
172 return linear_combination(t, p20, p21);
176 offset_pos(position p, GLfloat th, GLfloat r)
180 p.angle = (frand(2.0) - 1.0) * max_tilt;
184 /* Calculate new positions for all images.
187 set_new_positions(photopile_state *ss)
189 ModeInfo *mi = ss->mi;
192 for (i = 0; i < MI_COUNT(mi)+1; ++i)
194 image *frame = ss->frames + i;
195 GLfloat d = sqrt(frame->w*frame->w + frame->h*frame->h);
196 GLfloat leave = frand(M_PI * 2.0);
197 GLfloat enter = frand(M_PI * 2.0);
200 frame->pos[0] = frame->pos[3];
203 frame->pos[3].x = BELLRAND(MI_WIDTH(mi));
204 frame->pos[3].y = BELLRAND(MI_HEIGHT(mi));
205 frame->pos[3].angle = (frand(2.0) - 1.0) * max_tilt;
207 /* intermediate points */
208 frame->pos[1] = offset_pos(frame->pos[0], leave, d * (0.5 + frand(1.0)));
209 frame->pos[2] = offset_pos(frame->pos[3], enter, d * (0.5 + frand(1.0)));
213 /* Callback that tells us that the texture has been loaded.
216 image_loaded_cb (const char *filename, XRectangle *geom,
217 int image_width, int image_height,
218 int texture_width, int texture_height,
221 photopile_state *ss = (photopile_state *) closure;
222 ModeInfo *mi = ss->mi;
223 int wire = MI_IS_WIREFRAME(mi);
224 image *frame = ss->frames + ss->nframe;
228 frame->w = (int)(MI_WIDTH(mi) * scale) - 1;
229 frame->h = (int)(MI_HEIGHT(mi) * scale) - 1;
230 if (frame->w <= 10) frame->w = 10;
231 if (frame->h <= 10) frame->h = 10;
232 frame->geom.width = frame->w;
233 frame->geom.height = frame->h;
237 if (image_width == 0 || image_height == 0)
240 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
241 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
242 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
244 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
245 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
247 frame->w = image_width;
248 frame->h = image_height;
249 frame->tw = texture_width;
250 frame->th = texture_height;
255 frame->title = (filename ? strdup (filename) : 0);
257 if (frame->title) /* strip filename to part after last /. */
259 char *s = strrchr (frame->title, '/');
260 if (s) strcpy (frame->title, s+1);
264 fprintf (stderr, "%s: loaded %4d x %-4d %4d x %-4d \"%s\"\n",
268 frame->tw, frame->th,
269 (frame->title ? frame->title : "(null)"));
272 frame->loaded_p = True;
279 load_image (ModeInfo *mi)
281 photopile_state *ss = &sss[MI_SCREEN(mi)];
282 int wire = MI_IS_WIREFRAME(mi);
283 image *frame = ss->frames + ss->nframe;
285 if (debug_p && !wire && frame->w != 0)
286 fprintf (stderr, "%s: dropped %4d x %-4d %4d x %-4d \"%s\"\n",
290 frame->tw, frame->th,
291 (frame->title ? frame->title : "(null)"));
293 frame->loaded_p = False;
296 image_loaded_cb (0, 0, 0, 0, 0, 0, ss);
299 int w = MI_WIDTH(mi);
300 int h = MI_HEIGHT(mi);
301 int size = (int)((w > h ? w : h) * scale);
302 if (size <= 10) size = 10;
303 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
305 mipmap_p, frame->texid,
306 image_loaded_cb, ss);
312 loading_msg (ModeInfo *mi, int n)
314 photopile_state *ss = &sss[MI_SCREEN(mi)];
315 int wire = MI_IS_WIREFRAME(mi);
322 sprintf (text, "Loading images...");
324 sprintf (text, "Loading images... (%d%%)",
325 (int) (n * 100 / MI_COUNT(mi)));
327 if (ss->loading_sw == 0) /* only do this once, so that the string doesn't move. */
328 ss->loading_sw = texture_string_width (ss->texfont, text, &ss->loading_sh);
330 scale = ss->loading_sh / (GLfloat) MI_HEIGHT(mi);
332 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
334 glMatrixMode(GL_PROJECTION);
338 glMatrixMode(GL_MODELVIEW);
341 gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
343 glTranslatef ((MI_WIDTH(mi) - ss->loading_sw) / 2,
344 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
347 glEnable (GL_TEXTURE_2D);
348 glDisable (GL_DEPTH_TEST);
349 print_texture_string (ss->texfont, text);
350 glEnable (GL_DEPTH_TEST);
353 glMatrixMode(GL_PROJECTION);
356 glMatrixMode(GL_MODELVIEW);
359 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
364 load_initial_images (ModeInfo *mi)
366 photopile_state *ss = &sss[MI_SCREEN(mi)];
368 if (ss->frames[ss->nframe].loaded_p)
370 /* The current image has been fully loaded. */
371 if (++ss->nframe < MI_COUNT(mi))
373 /* Start the next one loading. (We run the image loader
374 * asynchronously, but we load them one at a time.)
380 /* loaded all initial images, start fading them in */
383 for (i = 0; i < ss->nframe; ++i)
385 ss->frames[i].pos[3].x = MI_WIDTH(mi) * 0.5;
386 ss->frames[i].pos[3].y = MI_HEIGHT(mi) * 0.5;
387 ss->frames[i].pos[3].angle = 0.0;
389 set_new_positions(ss);
392 ss->mode_tick = fade_ticks / speed;
396 loading_msg(mi, ss->nframe);
397 return (ss->mode != EARLY);
402 reshape_photopile (ModeInfo *mi, int width, int height)
404 glViewport (0, 0, (GLint) width, (GLint) height);
406 glMatrixMode(GL_PROJECTION);
409 glMatrixMode(GL_MODELVIEW);
411 gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
413 glClear(GL_COLOR_BUFFER_BIT);
417 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
420 hack_resources (Display *dpy)
423 char *res = "desktopGrabber";
424 char *val = get_string_resource (dpy, res, "DesktopGrabber");
428 XrmDatabase db = XtDatabase (dpy);
429 sprintf (buf1, "%.100s.%.100s", progname, res);
430 sprintf (buf2, "%.200s -v", val);
432 value.size = strlen(buf2);
433 XrmPutResource (&db, buf1, "String", &value);
434 # endif /* !HAVE_COCOA */
439 init_photopile (ModeInfo *mi)
441 int screen = MI_SCREEN(mi);
443 int wire = MI_IS_WIREFRAME(mi);
446 if ((sss = (photopile_state *)
447 calloc (MI_NUM_SCREENS(mi), sizeof(photopile_state))) == NULL)
453 if ((ss->glx_context = init_GL(mi)) != NULL) {
454 reshape_photopile (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
459 glDisable (GL_LIGHTING);
460 glEnable (GL_DEPTH_TEST);
461 glDisable (GL_CULL_FACE);
465 glShadeModel (GL_SMOOTH);
466 glEnable (GL_LINE_SMOOTH);
467 /* This gives us a transparent diagonal slice through each image! */
468 /* glEnable (GL_POLYGON_SMOOTH); */
469 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
471 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
473 glEnable (GL_POLYGON_OFFSET_FILL);
474 glPolygonOffset (1.0, 1.0);
477 ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
480 hack_resources (MI_DISPLAY (mi));
482 ss->frames = (image *)calloc (MI_COUNT(mi) + 1, sizeof(image));
487 for (i = 0; i < MI_COUNT(mi) + 1; ++i)
489 glGenTextures(1, &(ss->frames[i].texid));
490 if (ss->frames[i].texid <= 0) abort();
495 load_image(mi); /* start loading the first image */
500 draw_image (ModeInfo *mi, int i, GLfloat t, GLfloat s, GLfloat z)
502 int wire = MI_IS_WIREFRAME(mi);
503 photopile_state *ss = &sss[MI_SCREEN(mi)];
504 image *frame = ss->frames + i;
506 GLfloat texw = frame->geom.width / (GLfloat) frame->tw;
507 GLfloat texh = frame->geom.height / (GLfloat) frame->th;
508 GLfloat texx1 = frame->geom.x / (GLfloat) frame->tw;
509 GLfloat texy1 = frame->geom.y / (GLfloat) frame->th;
510 GLfloat texx2 = texx1 + texw;
511 GLfloat texy2 = texy1 + texh;
513 position pos = interpolate(t, frame->pos);
514 GLfloat w = frame->geom.width * 0.5;
515 GLfloat h = frame->geom.height * 0.5;
517 glBindTexture (GL_TEXTURE_2D, frame->texid);
521 /* Position and scale this image.
523 glTranslatef (pos.x, pos.y, 0);
524 glRotatef (pos.angle, 0, 0, 1);
527 /* Draw the image quad. */
531 glEnable (GL_TEXTURE_2D);
533 glTexCoord2f (texx1, texy2); glVertex3f (-w, -h, z);
534 glTexCoord2f (texx2, texy2); glVertex3f ( w, -h, z);
535 glTexCoord2f (texx2, texy1); glVertex3f ( w, h, z);
536 glTexCoord2f (texx1, texy1); glVertex3f (-w, h, z);
540 /* Draw a box around it.
543 glColor3f (0.5, 0.5, 0.5);
544 glDisable (GL_TEXTURE_2D);
545 glBegin (GL_LINE_LOOP);
546 glVertex3f (-w, -h, z);
547 glVertex3f ( w, -h, z);
548 glVertex3f ( w, h, z);
549 glVertex3f (-w, h, z);
552 /* Draw a title under the image.
558 char *title = frame->title ? frame->title : "(untitled)";
559 sw = texture_string_width (ss->texfont, title, &sh);
561 glTranslatef (-sw*scale*0.5, -h - sh*scale, z);
562 glScalef (scale, scale, 1);
568 glEnable (GL_TEXTURE_2D);
569 print_texture_string (ss->texfont, title);
573 glBegin (GL_LINE_LOOP);
574 glVertex3f (0, 0, 0);
575 glVertex3f (sw, 0, 0);
576 glVertex3f (sw, sh, 0);
577 glVertex3f (0, sh, 0);
587 draw_photopile (ModeInfo *mi)
589 photopile_state *ss = &sss[MI_SCREEN(mi)];
592 if (!ss->glx_context)
595 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
597 if (ss->mode == EARLY)
598 if (!load_initial_images (mi))
601 /* Only check the wall clock every 10 frames */
602 if (ss->now == 0 || ss->draw_tick++ > 10)
604 ss->now = time((time_t *) 0);
605 if (ss->last_time == 0) ss->last_time = ss->now;
609 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
613 /* Handle state transitions. */
617 if (--ss->mode_tick <= 0)
620 ss->last_time = time((time_t *) 0);
624 if (ss->now - ss->last_time > duration)
631 if (ss->frames[ss->nframe].loaded_p)
633 set_new_positions(ss);
635 ss->mode_tick = fade_ticks / speed;
639 if (--ss->mode_tick <= 0)
641 ss->nframe = (ss->nframe+1) % (MI_COUNT(mi)+1);
644 ss->last_time = time((time_t *) 0);
651 t = 1.0 - ss->mode_tick / (fade_ticks / speed);
652 t = 0.5 * (1.0 - cos(M_PI * t));
654 /* Draw the images. */
655 for (i = 0; i < MI_COUNT(mi) + (ss->mode == SHUFFLE); ++i)
657 int j = (ss->nframe + i + 1) % (MI_COUNT(mi) + 1);
659 GLfloat z = (GLfloat)i / (MI_COUNT(mi) + 1);
671 if (i == MI_COUNT(mi))
684 draw_image(mi, j, t, s, z);
688 if (mi->fps_p) do_fps (mi);
690 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
693 XSCREENSAVER_MODULE ("Photopile", photopile)