From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / hacks / glx / photopile.c
1 /* photopile, Copyright (c) 2008-2012 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-*-240-*"
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     ss->loading_sw = texture_string_width (ss->texfont, text, &ss->loading_sh);
357
358   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
359
360   glMatrixMode(GL_PROJECTION);
361   glPushMatrix();
362   glLoadIdentity();
363
364   glMatrixMode(GL_MODELVIEW);
365   glPushMatrix();
366   glLoadIdentity();
367   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
368
369   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
370                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
371                 0);
372   glColor3f (1, 1, 0);
373   glEnable (GL_TEXTURE_2D);
374   glDisable (GL_DEPTH_TEST);
375   print_texture_string (ss->texfont, text);
376   glEnable (GL_DEPTH_TEST);
377   glPopMatrix();
378
379   glMatrixMode(GL_PROJECTION);
380   glPopMatrix();
381
382   glMatrixMode(GL_MODELVIEW);
383
384   glFinish();
385   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
386 }
387
388
389 static Bool
390 loading_initial_image (ModeInfo *mi)
391 {
392   photopile_state *ss = &sss[MI_SCREEN(mi)];
393
394   if (ss->frames[ss->nframe].loaded_p)
395     {
396       /* The initial image has been fully loaded, start fading it in. */
397       int i;
398
399       for (i = 0; i < ss->nframe; ++i)
400         {
401           ss->frames[i].pos[3].x = MI_WIDTH(mi) * 0.5;
402           ss->frames[i].pos[3].y = MI_HEIGHT(mi) * 0.5;
403           ss->frames[i].pos[3].angle = 0.0;
404         }
405       set_new_positions(ss);
406
407       ss->mode = SHUFFLE;
408       ss->mode_tick = fade_ticks / speed;
409     }
410   else
411     {
412       loading_msg(mi);
413     }
414
415   return (ss->mode == EARLY);
416 }
417
418
419 ENTRYPOINT void
420 reshape_photopile (ModeInfo *mi, int width, int height)
421 {
422   glViewport (0, 0, (GLint) width, (GLint) height);
423
424   glMatrixMode(GL_PROJECTION);
425   glLoadIdentity();
426
427   glMatrixMode(GL_MODELVIEW);
428   glLoadIdentity();
429   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
430
431   glClear(GL_COLOR_BUFFER_BIT);
432 }
433
434
435 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
436  */
437 static void
438 hack_resources (Display *dpy)
439 {
440 # ifndef HAVE_COCOA
441   char *res = "desktopGrabber";
442   char *val = get_string_resource (dpy, res, "DesktopGrabber");
443   char buf1[255];
444   char buf2[255];
445   XrmValue value;
446   XrmDatabase db = XtDatabase (dpy);
447   sprintf (buf1, "%.100s.%.100s", progname, res);
448   sprintf (buf2, "%.200s -v", val);
449   value.addr = buf2;
450   value.size = strlen(buf2);
451   XrmPutResource (&db, buf1, "String", &value);
452 # endif /* !HAVE_COCOA */
453 }
454
455
456 ENTRYPOINT void
457 init_photopile (ModeInfo *mi)
458 {
459   int screen = MI_SCREEN(mi);
460   photopile_state *ss;
461   int wire = MI_IS_WIREFRAME(mi);
462
463   if (sss == NULL) {
464     if ((sss = (photopile_state *)
465          calloc (MI_NUM_SCREENS(mi), sizeof(photopile_state))) == NULL)
466       return;
467   }
468   ss = &sss[screen];
469   ss->mi = mi;
470
471   if ((ss->glx_context = init_GL(mi)) != NULL) {
472     reshape_photopile (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
473     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
474   } else {
475     MI_CLEARWINDOW(mi);
476   }
477
478   ss->shadow = init_drop_shadow();
479   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
480
481   if (debug_p)
482     hack_resources (MI_DISPLAY (mi));
483
484   ss->frames = (image *)calloc (MI_COUNT(mi) + 1, sizeof(image));
485   ss->nframe = 0;
486   if (!wire)
487     {
488       int i;
489       for (i = 0; i < MI_COUNT(mi) + 1; ++i)
490         {
491           glGenTextures (1, &(ss->frames[i].texid));
492           if (ss->frames[i].texid <= 0) abort();
493         }
494     }
495
496   ss->mode = EARLY;
497   load_image(mi); /* start loading the first image */
498 }
499
500
501 static void
502 draw_image (ModeInfo *mi, int i, GLfloat t, GLfloat s, GLfloat z)
503 {
504   int wire = MI_IS_WIREFRAME(mi);
505   photopile_state *ss = &sss[MI_SCREEN(mi)];
506   image *frame = ss->frames + i;
507
508   position pos = interpolate(t, frame->pos);
509   GLfloat w = frame->geom.width * 0.5;
510   GLfloat h = frame->geom.height * 0.5;
511   GLfloat z1 = z - 0.25 / (MI_COUNT(mi) + 1);
512   GLfloat z2 = z - 0.5  / (MI_COUNT(mi) + 1); 
513   GLfloat w1 = w;
514   GLfloat h1 = h;
515   GLfloat h2 = h;
516
517   if (polaroid_p)
518     {
519       GLfloat minSize = MIN(w, h);
520       GLfloat maxSize = MAX(w, h);
521
522       /* Clip or scale image to fit in the frame.
523        */
524       if (clip_p)
525         {
526           w = h = minSize;
527         }
528       else
529         {
530           GLfloat scale = minSize / maxSize;
531           w *= scale;
532           h *= scale;
533         }
534
535       w1 = minSize * 1.16;      /* enlarge frame border */
536       h1 = minSize * 1.5;
537       h2 = w1;
538       s /= 1.5;                 /* compensate for border size */
539     }
540
541   glPushMatrix();
542
543   /* Position and scale this image.
544    */
545   glTranslatef (pos.x, pos.y, 0);
546   glRotatef (pos.angle, 0, 0, 1);
547   glScalef (s, s, 1);
548
549   /* Draw the drop shadow. */
550   if (shadows_p && !wire)
551     {
552       glColor3f (0, 0, 0);
553       draw_drop_shadow(ss->shadow, -w1, -h1, z2, 2.0 * w1, h1 + h2, 20.0);
554       glDisable (GL_BLEND);
555     }
556
557   glDisable (GL_LIGHTING);
558   glEnable (GL_DEPTH_TEST);
559   glDisable (GL_CULL_FACE);
560
561   /* Draw the retro instant-film frame.
562    */
563   if (polaroid_p)
564     {
565       if (! wire)
566         {
567           glShadeModel (GL_SMOOTH);
568           glEnable (GL_LINE_SMOOTH);
569           glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
570
571           glColor3f (1, 1, 1);
572           glBegin (GL_QUADS);
573           glVertex3f (-w1, -h1, z2);
574           glVertex3f ( w1, -h1, z2);
575           glVertex3f ( w1,  h2, z2);
576           glVertex3f (-w1,  h2, z2);
577           glEnd();
578         }
579
580       glLineWidth (1.0);
581       glColor3f (0.5, 0.5, 0.5);
582       glBegin (GL_LINE_LOOP);
583       glVertex3f (-w1, -h1, z);
584       glVertex3f ( w1, -h1, z);
585       glVertex3f ( w1,  h2, z);
586       glVertex3f (-w1,  h2, z);
587       glEnd();
588     }
589
590   /* Draw the image quad.
591    */
592   if (! wire)
593     {
594       GLfloat texw = w / frame->tw;
595       GLfloat texh = h / frame->th;
596       GLfloat texx = (frame->geom.x + 0.5 * frame->geom.width)  / frame->tw;
597       GLfloat texy = (frame->geom.y + 0.5 * frame->geom.height) / frame->th;
598
599       glBindTexture (GL_TEXTURE_2D, frame->texid);
600       glEnable (GL_TEXTURE_2D);
601       glColor3f (1, 1, 1);
602       glBegin (GL_QUADS);
603       glTexCoord2f (texx - texw, texy + texh); glVertex3f (-w, -h, z1);
604       glTexCoord2f (texx + texw, texy + texh); glVertex3f ( w, -h, z1);
605       glTexCoord2f (texx + texw, texy - texh); glVertex3f ( w,  h, z1);
606       glTexCoord2f (texx - texw, texy - texh); glVertex3f (-w,  h, z1);
607       glEnd();
608       glDisable (GL_TEXTURE_2D);
609     }
610
611   /* Draw a box around it.
612    */
613   if (! wire)
614     {
615       glShadeModel (GL_SMOOTH);
616       glEnable (GL_LINE_SMOOTH);
617       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
618     }
619   glLineWidth (1.0);
620   glColor3f (0.5, 0.5, 0.5);
621   glBegin (GL_LINE_LOOP);
622   glVertex3f (-w, -h, z);
623   glVertex3f ( w, -h, z);
624   glVertex3f ( w,  h, z);
625   glVertex3f (-w,  h, z);
626   glEnd();
627
628   /* Draw a title under the image.
629    */
630   if (titles_p)
631     {
632       int sw, sh;
633       GLfloat scale = 0.6;
634       const char *title = frame->title ? frame->title : "(untitled)";
635
636       /* #### Highly approximate, but doing real clipping is harder... */
637       int max = 35;
638       if (strlen(title) > max)
639         title += strlen(title) - max;
640
641       sw = texture_string_width (ss->texfont, title, &sh);
642
643       glTranslatef (-sw*scale*0.5, -h - sh*scale, z);
644       glScalef (scale, scale, 1);
645
646       if (wire || !polaroid_p)
647         {
648           glColor3f (1, 1, 1);
649         }
650       else
651         {
652           glColor3f (0, 0, 0);
653         }
654
655       if (!wire)
656         {
657           glEnable (GL_TEXTURE_2D);
658           glEnable (GL_BLEND);
659           print_texture_string (ss->texfont, title);
660         }
661       else
662         {
663           glBegin (GL_LINE_LOOP);
664           glVertex3f (0,  0,  0);
665           glVertex3f (sw, 0,  0);
666           glVertex3f (sw, sh, 0);
667           glVertex3f (0,  sh, 0);
668           glEnd();
669         }
670     }
671
672   glPopMatrix();
673 }
674
675
676 ENTRYPOINT void
677 draw_photopile (ModeInfo *mi)
678 {
679   photopile_state *ss = &sss[MI_SCREEN(mi)];
680   int i;
681
682   if (!ss->glx_context)
683     return;
684
685   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
686
687   if (ss->mode == EARLY)
688     if (loading_initial_image (mi))
689       return;
690
691   /* Only check the wall clock every 10 frames */
692   if (ss->now == 0 || ss->draw_tick++ > 10)
693     {
694       ss->now = time((time_t *) 0);
695       if (ss->last_time == 0) ss->last_time = ss->now;
696       ss->draw_tick = 0;
697     }
698
699   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
700   {
701     GLfloat t;
702
703     glPushMatrix();
704     glTranslatef (MI_WIDTH(mi)/2, MI_HEIGHT(mi)/2, 0);
705     glRotatef(current_device_rotation(), 0, 0, 1);
706     glTranslatef (-MI_WIDTH(mi)/2, -MI_HEIGHT(mi)/2, 0);
707
708     /* Handle state transitions. */
709     switch (ss->mode)
710       {
711       case SHUFFLE:
712         if (--ss->mode_tick <= 0)
713           {
714             ss->nframe = (ss->nframe+1) % (MI_COUNT(mi)+1);
715
716             ss->mode = NORMAL;
717             ss->last_time = time((time_t *) 0);
718           }
719         break;
720       case NORMAL:
721         if (ss->now - ss->last_time > duration)
722           {
723             ss->mode = LOADING;
724             load_image(mi);
725           }
726         break;
727       case LOADING:
728         if (ss->frames[ss->nframe].loaded_p)
729           {
730             set_new_positions(ss);
731             ss->mode = SHUFFLE;
732             ss->mode_tick = fade_ticks / speed;
733           }
734         break;
735       default:
736         abort();
737       }
738
739     t = 1.0 - ss->mode_tick / (fade_ticks / speed);
740     t = 0.5 * (1.0 - cos(M_PI * t));
741
742     /* Draw the images. */
743     for (i = 0; i < MI_COUNT(mi) + (ss->mode == SHUFFLE); ++i)
744       {
745         int j = (ss->nframe + i + 1) % (MI_COUNT(mi) + 1);
746
747         if (ss->frames[j].loaded_p)
748           {
749             GLfloat s = 1.0;
750             GLfloat z = (GLfloat)i / (MI_COUNT(mi) + 1);
751
752             switch (ss->mode)
753               {
754               case SHUFFLE:
755                 if (i == MI_COUNT(mi))
756                   {
757                     s *= t;
758                   }
759                 else if (i == 0)
760                   {
761                     s *= 1.0 - t;
762                   }
763                 break;
764               case NORMAL:
765               case LOADING:
766                 t = 1.0;
767                 break;
768               default:
769                 abort();
770               }
771
772             draw_image(mi, j, t, s, z);
773           }
774       }
775     glPopMatrix();
776   }
777
778   if (mi->fps_p) do_fps (mi);
779   glFinish();
780   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
781 }
782
783 XSCREENSAVER_MODULE ("Photopile", photopile)
784
785 #endif /* USE_GL */