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