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