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