From http://www.jwz.org/xscreensaver/xscreensaver-5.33.tar.gz
[xscreensaver] / hacks / glx / photopile.c
1 /* photopile, Copyright (c) 2008-2015 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.
4  *
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
11  * implied warranty.
12  */
13
14 #define DEF_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*"
15 #define DEFAULTS  "*count:           7         \n" \
16                   "*delay:           10000     \n" \
17                   "*wireframe:       False     \n" \
18                   "*showFPS:         False     \n" \
19                   "*fpsSolid:        True      \n" \
20                   "*useSHM:          True      \n" \
21                   "*font:          " DEF_FONT "\n" \
22                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
23                   "*grabDesktopImages:   False \n" \
24                   "*chooseRandomImages:  True  \n"
25
26 # define refresh_photopile 0
27 # define release_photopile 0
28 # define photopile_handle_event 0
29
30 #undef countof
31 #define countof(x) (sizeof((x))/sizeof((*x)))
32
33 #ifndef HAVE_COCOA
34 # include <X11/Intrinsic.h>     /* for XrmDatabase in -debug mode */
35 #endif
36 #include <math.h>
37
38 #include "xlockmore.h"
39 #include "grab-ximage.h"
40 #include "texfont.h"
41 #include "dropshadow.h"
42
43 #ifdef USE_GL
44
45 # define DEF_SCALE          "0.4"
46 # define DEF_MAX_TILT       "50"
47 # define DEF_SPEED          "1.0"
48 # define DEF_DURATION       "5"
49 # define DEF_MIPMAP         "True"
50 # define DEF_TITLES         "False"
51 # define DEF_POLAROID       "True"
52 # define DEF_CLIP           "True"
53 # define DEF_SHADOWS        "True"
54 # define DEF_DEBUG          "False"
55
56 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
57
58 typedef struct {
59   GLfloat x, y;                 /* position on screen */
60   GLfloat angle;                /* rotation angle */
61
62 } position;
63
64 typedef struct {
65   Bool loaded_p;                /* true if image can be drawn */
66
67   char *title;                  /* the filename of this image */
68   int w, h;                     /* size in pixels of the image */
69   int tw, th;                   /* size in pixels of the texture */
70   XRectangle geom;              /* where in the image the bits are */
71
72   position pos[4];              /* control points for calculating position */
73
74   GLuint texid;                 /* GL texture ID */
75
76 } image;
77
78
79 typedef enum { EARLY, SHUFFLE, NORMAL, LOADING } fade_mode;
80 static int fade_ticks = 60;
81
82 typedef struct {
83   ModeInfo *mi;
84   GLXContext *glx_context;
85
86   image *frames;                /* pointer to array of images */
87   int nframe;                   /* image being (resp. next to be) loaded */
88
89   GLuint shadow;
90   texture_font_data *texfont;
91   int loading_sw, loading_sh;
92
93   time_t last_time, now;
94   int draw_tick;
95   fade_mode mode;
96   int mode_tick;
97
98 } photopile_state;
99
100 static photopile_state *sss = NULL;
101
102
103 /* Command-line arguments
104  */
105 static GLfloat scale;        /* Scale factor for loading images. */
106 static GLfloat max_tilt;     /* Maximum angle from vertical. */
107 static GLfloat speed;        /* Animation speed scale factor. */
108 static int duration;         /* Reload images after this long. */
109 static Bool mipmap_p;        /* Use mipmaps instead of single textures. */
110 static Bool titles_p;        /* Display image titles. */
111 static Bool polaroid_p;      /* Use instant-film look for images. */
112 static Bool clip_p;          /* Clip images instead of scaling for -polaroid. */
113 static Bool shadows_p;       /* Draw drop shadows. */
114 static Bool debug_p;         /* Be loud and do weird things. */
115
116
117 static XrmOptionDescRec opts[] = {
118   {"-scale",        ".scale",         XrmoptionSepArg, 0 },
119   {"-maxTilt",      ".maxTilt",       XrmoptionSepArg, 0 },
120   {"-speed",        ".speed",         XrmoptionSepArg, 0 },
121   {"-duration",     ".duration",      XrmoptionSepArg, 0 },
122   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
123   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
124   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
125   {"-no-titles",    ".titles",        XrmoptionNoArg, "False" },
126   {"-polaroid",     ".polaroid",      XrmoptionNoArg, "True"  },
127   {"-no-polaroid",  ".polaroid",      XrmoptionNoArg, "False" },
128   {"-clip",         ".clip",          XrmoptionNoArg, "True"  },
129   {"-no-clip",      ".clip",          XrmoptionNoArg, "False" },
130   {"-shadows",      ".shadows",       XrmoptionNoArg, "True"  },
131   {"-no-shadows",   ".shadows",       XrmoptionNoArg, "False" },
132   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
133   {"-font",         ".font",          XrmoptionSepArg, 0 },
134 };
135
136 static argtype vars[] = {
137   { &scale,         "scale",        "Scale",        DEF_SCALE,       t_Float},
138   { &max_tilt,      "maxTilt",      "MaxTilt",      DEF_MAX_TILT,    t_Float},
139   { &speed,         "speed",        "Speed",        DEF_SPEED,       t_Float},
140   { &duration,      "duration",     "Duration",     DEF_DURATION,    t_Int},
141   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,      t_Bool},
142   { &titles_p,      "titles",       "Titles",       DEF_TITLES,      t_Bool},
143   { &polaroid_p,    "polaroid",     "Polaroid",     DEF_POLAROID,    t_Bool},
144   { &clip_p,        "clip",         "Clip",         DEF_CLIP,        t_Bool},
145   { &shadows_p,     "shadows",      "Shadows",      DEF_SHADOWS,     t_Bool},
146   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,       t_Bool},
147 };
148
149 ENTRYPOINT ModeSpecOpt photopile_opts = {countof(opts), opts, countof(vars), vars, NULL};
150
151
152 /* Functions to interpolate between image positions.
153  */
154 static position
155 add_pos(position p, position q)
156 {
157   p.x += q.x;
158   p.y += q.y;
159   p.angle += q.angle;
160   return p;
161 }
162
163 static position
164 scale_pos(GLfloat t, position p)
165 {
166   p.x *= t;
167   p.y *= t;
168   p.angle *= t;
169   return p;
170 }
171
172 static position
173 linear_combination(GLfloat t, position p, position q)
174 {
175   return add_pos(scale_pos(1.0 - t, p), scale_pos(t, q));
176 }
177
178 static position
179 interpolate(GLfloat t, position p[4])
180 {
181   /* de Casteljau's algorithm, 4 control points */
182   position p10 = linear_combination(t, p[0], p[1]);
183   position p11 = linear_combination(t, p[1], p[2]);
184   position p12 = linear_combination(t, p[2], p[3]);
185
186   position p20 = linear_combination(t, p10, p11);
187   position p21 = linear_combination(t, p11, p12);
188
189   return linear_combination(t, p20, p21);
190 }
191
192 static position
193 offset_pos(position p, GLfloat th, GLfloat r)
194 {
195   p.x += cos(th) * r;
196   p.y += sin(th) * r;
197   p.angle = (frand(2.0) - 1.0) * max_tilt;
198   return p;
199 }
200
201 /* Calculate new positions for all images.
202  */
203 static void
204 set_new_positions(photopile_state *ss)
205 {
206   ModeInfo *mi = ss->mi;
207   int i;
208
209   for (i = 0; i < MI_COUNT(mi)+1; ++i)
210     {
211       image *frame = ss->frames + i;
212       GLfloat w = frame->w;
213       GLfloat h = frame->h;
214       GLfloat d = sqrt(w*w + h*h);
215       GLfloat leave = frand(M_PI * 2.0);
216       GLfloat enter = frand(M_PI * 2.0);
217
218       /* start position */
219       frame->pos[0] = frame->pos[3];
220
221       /* end position */
222       frame->pos[3].x = BELLRAND(MI_WIDTH(mi));
223       frame->pos[3].y = BELLRAND(MI_HEIGHT(mi));
224       frame->pos[3].angle = (frand(2.0) - 1.0) * max_tilt;
225
226       /* Try to keep the images mostly inside the screen bounds */
227       frame->pos[3].x = MAX(0.5*w, MIN(MI_WIDTH(mi)-0.5*w, frame->pos[3].x));
228       frame->pos[3].y = MAX(0.5*h, MIN(MI_HEIGHT(mi)-0.5*h, frame->pos[3].y));
229
230       /* intermediate points */
231       frame->pos[1] = offset_pos(frame->pos[0], leave, d * (0.5 + frand(1.0)));
232       frame->pos[2] = offset_pos(frame->pos[3], enter, d * (0.5 + frand(1.0)));
233     }
234 }
235
236 /* Callback that tells us that the texture has been loaded.
237  */
238 static void
239 image_loaded_cb (const char *filename, XRectangle *geom,
240                  int image_width, int image_height,
241                  int texture_width, int texture_height,
242                  void *closure)
243 {
244   photopile_state *ss = (photopile_state *) closure;
245   ModeInfo *mi = ss->mi;
246   int wire = MI_IS_WIREFRAME(mi);
247   image *frame = ss->frames + ss->nframe;
248
249   if (wire)
250     {
251       if (random() % 2)
252         {
253           frame->w = (int)(MI_WIDTH(mi)  * scale) - 1;
254           frame->h = (int)(MI_HEIGHT(mi) * scale) - 1;
255         }
256       else
257         {
258           frame->w = (int)(MI_HEIGHT(mi) * scale) - 1;
259           frame->h = (int)(MI_WIDTH(mi)  * scale) - 1;
260         }
261       if (frame->w <= 10) frame->w = 10;
262       if (frame->h <= 10) frame->h = 10;
263       frame->geom.width  = frame->w;
264       frame->geom.height = frame->h;
265       goto DONE;
266     }
267
268   if (image_width == 0 || image_height == 0)
269     exit (1);
270
271   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
272   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
273                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
274
275   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
276   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
277
278   frame->w  = image_width;
279   frame->h  = image_height;
280   frame->tw = texture_width;
281   frame->th = texture_height;
282   frame->geom = *geom;
283
284   if (frame->title)
285     free (frame->title);
286   frame->title = (filename ? strdup (filename) : 0);
287
288   /* xscreensaver-getimage returns paths relative to the image directory
289      now, so leave the sub-directory part in.  Unless it's an absolute path.
290   */
291   if (frame->title && frame->title[0] == '/')
292     {
293       /* strip filename to part after last /. */
294       char *s = strrchr (frame->title, '/');
295       if (s) strcpy (frame->title, s+1);
296     }
297
298   if (debug_p)
299     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
300              progname,
301              frame->geom.width, 
302              frame->geom.height, 
303              frame->tw, frame->th,
304              (frame->title ? frame->title : "(null)"));
305
306  DONE:
307   frame->loaded_p = True;
308 }
309
310
311 /* Load a new file.
312  */
313 static void
314 load_image (ModeInfo *mi)
315 {
316   photopile_state *ss = &sss[MI_SCREEN(mi)];
317   int wire = MI_IS_WIREFRAME(mi);
318   image *frame = ss->frames + ss->nframe;
319
320   if (debug_p && !wire && frame->w != 0)
321     fprintf (stderr, "%s:  dropped %4d x %-4d  %4d x %-4d  \"%s\"\n",
322              progname, 
323              frame->geom.width, 
324              frame->geom.height, 
325              frame->tw, frame->th,
326              (frame->title ? frame->title : "(null)"));
327
328   frame->loaded_p = False;
329
330   if (wire)
331     image_loaded_cb (0, 0, 0, 0, 0, 0, ss);
332   else
333     {
334       int w = MI_WIDTH(mi);
335       int h = MI_HEIGHT(mi);
336       int size = (int)((w > h ? w : h) * scale);
337       if (size <= 10) size = 10;
338       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
339                           size, size,
340                           mipmap_p, frame->texid, 
341                           image_loaded_cb, ss);
342     }
343 }
344
345
346 static void
347 loading_msg (ModeInfo *mi)
348 {
349   photopile_state *ss = &sss[MI_SCREEN(mi)];
350   int wire = MI_IS_WIREFRAME(mi);
351   const char text[] = "Loading...";
352
353   if (wire) return;
354
355   if (ss->loading_sw == 0)    /* only do this once */
356     {
357       XCharStruct e;
358       texture_string_metrics (ss->texfont, text, &e, 0, 0);
359       ss->loading_sw = e.width;
360       ss->loading_sh = e.ascent + e.descent;
361     }
362
363   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
364
365   glMatrixMode(GL_PROJECTION);
366   glPushMatrix();
367   glLoadIdentity();
368
369   glMatrixMode(GL_MODELVIEW);
370   glPushMatrix();
371   glLoadIdentity();
372   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
373
374   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
375                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
376                 0);
377   glColor3f (1, 1, 0);
378   glEnable (GL_TEXTURE_2D);
379   glDisable (GL_DEPTH_TEST);
380   print_texture_string (ss->texfont, text);
381   glEnable (GL_DEPTH_TEST);
382   glPopMatrix();
383
384   glMatrixMode(GL_PROJECTION);
385   glPopMatrix();
386
387   glMatrixMode(GL_MODELVIEW);
388
389   glFinish();
390   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
391 }
392
393
394 static Bool
395 loading_initial_image (ModeInfo *mi)
396 {
397   photopile_state *ss = &sss[MI_SCREEN(mi)];
398
399   if (ss->frames[ss->nframe].loaded_p)
400     {
401       /* The initial image has been fully loaded, start fading it in. */
402       int i;
403
404       for (i = 0; i < ss->nframe; ++i)
405         {
406           ss->frames[i].pos[3].x = MI_WIDTH(mi) * 0.5;
407           ss->frames[i].pos[3].y = MI_HEIGHT(mi) * 0.5;
408           ss->frames[i].pos[3].angle = 0.0;
409         }
410       set_new_positions(ss);
411
412       ss->mode = SHUFFLE;
413       ss->mode_tick = fade_ticks / speed;
414     }
415   else
416     {
417       loading_msg(mi);
418     }
419
420   return (ss->mode == EARLY);
421 }
422
423
424 ENTRYPOINT void
425 reshape_photopile (ModeInfo *mi, int width, int height)
426 {
427   glViewport (0, 0, (GLint) width, (GLint) height);
428
429   glMatrixMode(GL_PROJECTION);
430   glLoadIdentity();
431
432   glMatrixMode(GL_MODELVIEW);
433   glLoadIdentity();
434   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
435
436   glClear(GL_COLOR_BUFFER_BIT);
437 }
438
439
440 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
441  */
442 static void
443 hack_resources (Display *dpy)
444 {
445 # ifndef HAVE_COCOA
446   char *res = "desktopGrabber";
447   char *val = get_string_resource (dpy, res, "DesktopGrabber");
448   char buf1[255];
449   char buf2[255];
450   XrmValue value;
451   XrmDatabase db = XtDatabase (dpy);
452   sprintf (buf1, "%.100s.%.100s", progname, res);
453   sprintf (buf2, "%.200s -v", val);
454   value.addr = buf2;
455   value.size = strlen(buf2);
456   XrmPutResource (&db, buf1, "String", &value);
457 # endif /* !HAVE_COCOA */
458 }
459
460
461 ENTRYPOINT void
462 init_photopile (ModeInfo *mi)
463 {
464   int screen = MI_SCREEN(mi);
465   photopile_state *ss;
466   int wire = MI_IS_WIREFRAME(mi);
467
468   if (sss == NULL) {
469     if ((sss = (photopile_state *)
470          calloc (MI_NUM_SCREENS(mi), sizeof(photopile_state))) == NULL)
471       return;
472   }
473   ss = &sss[screen];
474   ss->mi = mi;
475
476   if ((ss->glx_context = init_GL(mi)) != NULL) {
477     reshape_photopile (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
478     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
479   } else {
480     MI_CLEARWINDOW(mi);
481   }
482
483   ss->shadow = init_drop_shadow();
484   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
485
486   if (debug_p)
487     hack_resources (MI_DISPLAY (mi));
488
489   ss->frames = (image *)calloc (MI_COUNT(mi) + 1, sizeof(image));
490   ss->nframe = 0;
491   if (!wire)
492     {
493       int i;
494       for (i = 0; i < MI_COUNT(mi) + 1; ++i)
495         {
496           glGenTextures (1, &(ss->frames[i].texid));
497           if (ss->frames[i].texid <= 0) abort();
498         }
499     }
500
501   ss->mode = EARLY;
502   load_image(mi); /* start loading the first image */
503 }
504
505
506 static void
507 draw_image (ModeInfo *mi, int i, GLfloat t, GLfloat s, GLfloat z)
508 {
509   int wire = MI_IS_WIREFRAME(mi);
510   photopile_state *ss = &sss[MI_SCREEN(mi)];
511   image *frame = ss->frames + i;
512
513   position pos = interpolate(t, frame->pos);
514   GLfloat w = frame->geom.width * 0.5;
515   GLfloat h = frame->geom.height * 0.5;
516   GLfloat z1 = z - 0.25 / (MI_COUNT(mi) + 1);
517   GLfloat z2 = z - 0.5  / (MI_COUNT(mi) + 1); 
518   GLfloat w1 = w;
519   GLfloat h1 = h;
520   GLfloat h2 = h;
521
522   if (polaroid_p)
523     {
524       GLfloat minSize = MIN(w, h);
525       GLfloat maxSize = MAX(w, h);
526
527       /* Clip or scale image to fit in the frame.
528        */
529       if (clip_p)
530         {
531           w = h = minSize;
532         }
533       else
534         {
535           GLfloat scale = minSize / maxSize;
536           w *= scale;
537           h *= scale;
538         }
539
540       w1 = minSize * 1.16;      /* enlarge frame border */
541       h1 = minSize * 1.5;
542       h2 = w1;
543       s /= 1.5;                 /* compensate for border size */
544     }
545
546   glPushMatrix();
547
548   /* Position and scale this image.
549    */
550   glTranslatef (pos.x, pos.y, 0);
551   glRotatef (pos.angle, 0, 0, 1);
552   glScalef (s, s, 1);
553
554   /* Draw the drop shadow. */
555   if (shadows_p && !wire)
556     {
557       glColor3f (0, 0, 0);
558       draw_drop_shadow(ss->shadow, -w1, -h1, z2, 2.0 * w1, h1 + h2, 20.0);
559       glDisable (GL_BLEND);
560     }
561
562   glDisable (GL_LIGHTING);
563   glEnable (GL_DEPTH_TEST);
564   glDisable (GL_CULL_FACE);
565
566   /* Draw the retro instant-film frame.
567    */
568   if (polaroid_p)
569     {
570       if (! wire)
571         {
572           glShadeModel (GL_SMOOTH);
573           glEnable (GL_LINE_SMOOTH);
574           glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
575
576           glColor3f (1, 1, 1);
577           glBegin (GL_QUADS);
578           glVertex3f (-w1, -h1, z2);
579           glVertex3f ( w1, -h1, z2);
580           glVertex3f ( w1,  h2, z2);
581           glVertex3f (-w1,  h2, z2);
582           glEnd();
583         }
584
585       glLineWidth (1.0);
586       glColor3f (0.5, 0.5, 0.5);
587       glBegin (GL_LINE_LOOP);
588       glVertex3f (-w1, -h1, z);
589       glVertex3f ( w1, -h1, z);
590       glVertex3f ( w1,  h2, z);
591       glVertex3f (-w1,  h2, z);
592       glEnd();
593     }
594
595   /* Draw the image quad.
596    */
597   if (! wire)
598     {
599       GLfloat texw = w / frame->tw;
600       GLfloat texh = h / frame->th;
601       GLfloat texx = (frame->geom.x + 0.5 * frame->geom.width)  / frame->tw;
602       GLfloat texy = (frame->geom.y + 0.5 * frame->geom.height) / frame->th;
603
604       glBindTexture (GL_TEXTURE_2D, frame->texid);
605       glEnable (GL_TEXTURE_2D);
606       glColor3f (1, 1, 1);
607       glBegin (GL_QUADS);
608       glTexCoord2f (texx - texw, texy + texh); glVertex3f (-w, -h, z1);
609       glTexCoord2f (texx + texw, texy + texh); glVertex3f ( w, -h, z1);
610       glTexCoord2f (texx + texw, texy - texh); glVertex3f ( w,  h, z1);
611       glTexCoord2f (texx - texw, texy - texh); glVertex3f (-w,  h, z1);
612       glEnd();
613       glDisable (GL_TEXTURE_2D);
614     }
615
616   /* Draw a box around it.
617    */
618   if (! wire)
619     {
620       glShadeModel (GL_SMOOTH);
621       glEnable (GL_LINE_SMOOTH);
622       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
623     }
624   glLineWidth (1.0);
625   glColor3f (0.5, 0.5, 0.5);
626   glBegin (GL_LINE_LOOP);
627   glVertex3f (-w, -h, z);
628   glVertex3f ( w, -h, z);
629   glVertex3f ( w,  h, z);
630   glVertex3f (-w,  h, z);
631   glEnd();
632
633   /* Draw a title under the image.
634    */
635   if (titles_p)
636     {
637       int sw, sh, ascent, descent;
638       GLfloat scale = 1;
639       const char *title = frame->title ? frame->title : "(untitled)";
640       XCharStruct e;
641
642       /* #### Highly approximate, but doing real clipping is harder... */
643       int max = 35;
644       if (strlen(title) > max)
645         title += strlen(title) - max;
646
647       texture_string_metrics (ss->texfont, title, &e, &ascent, &descent);
648       sw = e.width;
649       sh = ascent + descent;
650
651       /* Scale the text to match the pixel size of the photo */
652       scale *= w / 300.0;
653
654       /* Move to below photo */
655       glTranslatef (0, -h - sh * (polaroid_p ? 2.2 : 0.5), 0);
656       glTranslatef (-sw*scale/2, sh*scale/2, z);
657       glScalef (scale, scale, 1);
658
659       if (wire || !polaroid_p)
660         {
661           glColor3f (1, 1, 1);
662         }
663       else
664         {
665           glColor3f (0, 0, 0);
666         }
667
668       if (!wire)
669         {
670           glEnable (GL_TEXTURE_2D);
671           glEnable (GL_BLEND);
672           print_texture_string (ss->texfont, title);
673         }
674       else
675         {
676           glBegin (GL_LINE_LOOP);
677           glVertex3f (0,  0,  0);
678           glVertex3f (sw, 0,  0);
679           glVertex3f (sw, sh, 0);
680           glVertex3f (0,  sh, 0);
681           glEnd();
682         }
683     }
684
685   glPopMatrix();
686 }
687
688
689 ENTRYPOINT void
690 draw_photopile (ModeInfo *mi)
691 {
692   photopile_state *ss = &sss[MI_SCREEN(mi)];
693   int i;
694
695   if (!ss->glx_context)
696     return;
697
698   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
699
700   if (ss->mode == EARLY)
701     if (loading_initial_image (mi))
702       return;
703
704   /* Only check the wall clock every 10 frames */
705   if (ss->now == 0 || ss->draw_tick++ > 10)
706     {
707       ss->now = time((time_t *) 0);
708       if (ss->last_time == 0) ss->last_time = ss->now;
709       ss->draw_tick = 0;
710     }
711
712   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
713   {
714     GLfloat t;
715
716     glPushMatrix();
717     glTranslatef (MI_WIDTH(mi)/2, MI_HEIGHT(mi)/2, 0);
718     glRotatef(current_device_rotation(), 0, 0, 1);
719     glTranslatef (-MI_WIDTH(mi)/2, -MI_HEIGHT(mi)/2, 0);
720
721     /* Handle state transitions. */
722     switch (ss->mode)
723       {
724       case SHUFFLE:
725         if (--ss->mode_tick <= 0)
726           {
727             ss->nframe = (ss->nframe+1) % (MI_COUNT(mi)+1);
728
729             ss->mode = NORMAL;
730             ss->last_time = time((time_t *) 0);
731           }
732         break;
733       case NORMAL:
734         if (ss->now - ss->last_time > duration)
735           {
736             ss->mode = LOADING;
737             load_image(mi);
738           }
739         break;
740       case LOADING:
741         if (ss->frames[ss->nframe].loaded_p)
742           {
743             set_new_positions(ss);
744             ss->mode = SHUFFLE;
745             ss->mode_tick = fade_ticks / speed;
746           }
747         break;
748       default:
749         abort();
750       }
751
752     t = 1.0 - ss->mode_tick / (fade_ticks / speed);
753     t = 0.5 * (1.0 - cos(M_PI * t));
754
755     /* Draw the images. */
756     for (i = 0; i < MI_COUNT(mi) + (ss->mode == SHUFFLE); ++i)
757       {
758         int j = (ss->nframe + i + 1) % (MI_COUNT(mi) + 1);
759
760         if (ss->frames[j].loaded_p)
761           {
762             GLfloat s = 1.0;
763             GLfloat z = (GLfloat)i / (MI_COUNT(mi) + 1);
764
765             switch (ss->mode)
766               {
767               case SHUFFLE:
768                 if (i == MI_COUNT(mi))
769                   {
770                     s *= t;
771                   }
772                 else if (i == 0)
773                   {
774                     s *= 1.0 - t;
775                   }
776                 break;
777               case NORMAL:
778               case LOADING:
779                 t = 1.0;
780                 break;
781               default:
782                 abort();
783               }
784
785             draw_image(mi, j, t, s, z);
786           }
787       }
788     glPopMatrix();
789   }
790
791   if (mi->fps_p) do_fps (mi);
792   glFinish();
793   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
794 }
795
796 XSCREENSAVER_MODULE ("Photopile", photopile)
797
798 #endif /* USE_GL */