示例#1
0
static void
init_textures (ModeInfo *mi)
{
  splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
  int i;
  const char * const *spool = latin1_spool;
  int max = countof(latin1_spool);

  bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
  texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);

  for (i = 0; i < max; i++)
    {
      texinfo *ti = &bp->texinfo[i];
      glGenTextures (1, &ti->texid);
      glBindTexture (GL_TEXTURE_2D, ti->texid);

      ti->text = spool[i];

      /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
              (unsigned char) ti->text[0],
              (unsigned char) ti->text[1],
              ti->text); */

      string_to_texture (bp->font_data, ti->text, &ti->metrics,
                         &ti->tex_width, &ti->tex_height);
    }
  bp->texinfo_size = i;

  glBindTexture (GL_TEXTURE_2D, 0);
}
示例#2
0
static int
char_width (fliptext_configuration *sc, char c)
{
  XCharStruct e;
  char s[2];
  s[0] = c;
  s[1] = 0;
  texture_string_metrics (sc->texfont, s, &e, 0, 0);
  return e.width;
}
示例#3
0
static int
draw_colon (ModeInfo *mi)
{
  splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
  GLfloat s = 1.0 / (bp->ascent + bp->descent);
  GLfloat z = 0.01;
  int count = 0;
  XCharStruct m;

  texture_string_metrics (bp->font_data, ":", &m, 0, 0);

  s *= 2;

  glPushMatrix();

  glTranslatef (-(1 + COLON_WIDTH), 0, 0);
  glScalef (s, s, 1);

  glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
                -(m.ascent + m.descent) / 2,
                0);

  glEnable (GL_TEXTURE_2D);

  /* draw the text five times, to give it a border. */
  {
    const XPoint offsets[] = {{ -1, -1 },
                              { -1,  1 },
                              {  1,  1 },
                              {  1, -1 },
                              {  0,  0 }};
    int i;
    GLfloat n = 1.5;

    glColor3f (0, 0, 0);
    for (i = 0; i < countof(offsets); i++)
      {
        glPushMatrix();
        if (offsets[i].x == 0)
          {
            glColor4fv (bp->text_color);
            glTranslatef (0, 0, z * 2);
          }
        glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
        print_texture_string (bp->font_data, ":");
        count++;
        glPopMatrix();
      }
  }

  glPopMatrix();

  return count;
}
示例#4
0
static void
draw_unichar (ModeInfo *mi)
{
  unicrud_configuration *bp = &bps[MI_SCREEN(mi)];

  char text[10];
  char title[400];
  XCharStruct e;
  int w, h, i, j;
  GLfloat s;

  i = utf8_encode (bp->unichar, text, sizeof(text) - 1);
  text[i] = 0;

  *title = 0;
  sprintf (title + strlen(title), "Plane:\t%s\n", bp->charplane);
  sprintf (title + strlen(title), "Block:\t%s\n", bp->charblock);
# ifdef HAVE_JWXYZ
  sprintf (title + strlen(title), "Name:\t%s\n",
           (bp->charname ? bp->charname : ""));
#endif
  sprintf (title + strlen(title), "Unicode:\t%04lX\n", bp->unichar);
  sprintf (title + strlen(title), "UTF-8:\t");
  for (j = 0; j < i; j++)
    sprintf (title + strlen(title), "%02X ", ((unsigned char *)text)[j]);

  texture_string_metrics (bp->char_font, text, &e, 0, 0);
  w = e.width;
  h = e.ascent;

  s = 9;
  glScalef (s, s, s);
  
  s = 1.0 / (h > w ? h : w);	/* Scale to unit */
  glScalef (s, s, s);

  glTranslatef (-w/2, -h/2, 0);
  glColor4fv (bp->color);
  print_texture_string (bp->char_font, text);

  glColor3f (1, 1, 0);
  if (do_titles)
    print_texture_label (mi->dpy, bp->title_font,
                         mi->xgwa.width, mi->xgwa.height,
                         1, title);
}
示例#5
0
static void
loading_msg (ModeInfo *mi)
{
  photopile_state *ss = &sss[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  const char text[] = "Loading...";

  if (wire) return;

  if (ss->loading_sw == 0)    /* only do this once */
    {
      XCharStruct e;
      texture_string_metrics (ss->texfont, text, &e, 0, 0);
      ss->loading_sw = e.width;
      ss->loading_sh = e.ascent + e.descent;
    }

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);

  glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
                (MI_HEIGHT(mi) - ss->loading_sh) / 2,
                0);
  glColor3f (1, 1, 0);
  glEnable (GL_TEXTURE_2D);
  glDisable (GL_DEPTH_TEST);
  print_texture_string (ss->texfont, text);
  glEnable (GL_DEPTH_TEST);
  glPopMatrix();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);

  glFinish();
  glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
}
示例#6
0
/* Reads some text from the subprocess, and creates and returns a `line'
   object.  Adds that object to the lines list.   Returns 0 if no text
   available yet.

   If skip_blanks_p, then keep trying for new lines of text until we
   get one that is not empty.
 */
static line *
make_line (fliptext_configuration *sc, Bool skip_blanks_p)
{
  XCharStruct e;
  line *ln;
  char *s;

 AGAIN:
  s = get_one_line (sc);
  if (s && skip_blanks_p && blank_p (s))
    {
      free (s);
      goto AGAIN;
    }

  if (!s) return 0;

  ln = (line *) calloc (1, sizeof(*ln));
  ln->text = s;
  ln->state = NEW;
  texture_string_metrics (sc->texfont, s, &e, 0, 0);
  ln->width = sc->font_scale * e.width;
  ln->height = sc->font_scale * sc->line_height;

  memcpy (ln->color, sc->color, sizeof(ln->color));

  sc->nlines++;
  if (sc->lines_size <= sc->nlines)
    {
      sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
      sc->lines = (line **)
        realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
      if (! sc->lines)
        {
          fprintf (stderr, "%s: out of memory (%d lines)\n",
                   progname, sc->lines_size);
          exit (1);
        }
    }

  sc->lines[sc->nlines-1] = ln;
  return ln;
}
示例#7
0
static void
pick_unichar (ModeInfo *mi)
{
  unicrud_configuration *bp = &bps[MI_SCREEN(mi)];
  int i;
  unsigned long min = 0;
  unsigned long max = 0x2F800;
  unsigned long last = 0;
  int retries = 0;

 AGAIN:
  bp->unichar = min + (random() % (max - min));

  if (++retries > 0xF0000 / 2)
    {
      fprintf (stderr, "%s: internal error: too many retries\n", progname);
      exit (1);
    }

  /* bp->unichar = 0x1F4A9; */

  last = 0;
  bp->charplane = "Unassigned";
  bp->charblock = "Unassigned";
  for (i = 0; i < countof(unicode_block_names); i++)
    {
      if (unicode_block_names[i].start < last)
        {
          fprintf (stderr, "%s: progname: internal error: misordered: 0x%lX\n",
                   progname, unicode_block_names[i].start);
          exit (1);
        }
      last = unicode_block_names[i].start;
      if (bp->unichar >= unicode_block_names[i].start)
        {
          if (unicode_block_names[i].name[0] == '*')
            {
              bp->charplane = unicode_block_names[i].name + 1;
              bp->charblock = "Unassigned";
            }
          else
            bp->charblock = unicode_block_names[i].name;
        }
      else
        break;
    }

  if (!strncmp (bp->charblock, "Unassigned", 10) ||
      !strncmp (bp->charblock, "Combining", 9))
    goto AGAIN;

  if (*do_block && !matches (do_block, bp->charblock))
    goto AGAIN;

  /* Skip blank characters */
  {
    XCharStruct e;
    char text[10];
    i = utf8_encode (bp->unichar, text, sizeof(text) - 1);
    text[i] = 0;
    texture_string_metrics (bp->char_font, text, &e, 0, 0);
    if (e.width < 2 || e.ascent + e.descent < 2)
      goto AGAIN;
  }

# ifdef HAVE_JWXYZ
  bp->charname = texfont_unicode_character_name (bp->char_font, bp->unichar);
# endif

  bp->color[0] = 0.5 + frand(0.5);
  bp->color[1] = 0.5 + frand(0.5);
  bp->color[2] = 0.5 + frand(0.5);
  bp->color[3] = 1;
}
示例#8
0
static void
draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p)
{
  carousel_state *ss = &sss[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);

  GLfloat texw  = frame->current.geom.width  / (GLfloat) frame->current.tw;
  GLfloat texh  = frame->current.geom.height / (GLfloat) frame->current.th;
  GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw;
  GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th;
  GLfloat texx2 = texx1 + texw;
  GLfloat texy2 = texy1 + texh;
  GLfloat aspect = ((GLfloat) frame->current.geom.height /
                    (GLfloat) frame->current.geom.width);

  glBindTexture (GL_TEXTURE_2D, frame->current.texid);

  glPushMatrix();

  /* Position this image on the wheel.
   */
  glRotatef (frame->theta, 0, 1, 0);
  glTranslatef (0, 0, frame->r);

  /* Scale down the image so that all N frames fit on the wheel
     without bumping in to each other.
  */
  {
    GLfloat t, s;
    switch (ss->nframes)
      {
      case 1:  t = -1.0; s = 1.7; break;
      case 2:  t = -0.8; s = 1.6; break;
      case 3:  t = -0.4; s = 1.5; break;
      case 4:  t = -0.2; s = 1.3; break;
      default: t =  0.0; s = 6.0 / ss->nframes; break;
      }
    glTranslatef (0, 0, t);
    glScalef (s, s, s);
  }

  /* Center this image on the wheel plane.
   */
  glTranslatef (-0.5, -(aspect/2), 0);

  /* Move as per the "zoom in and out" setting.
   */
  if (zoom_p)
    {
      double x, y, z;
      /* Only use the Z component of the rotator for in/out position. */
      get_position (frame->rot, &x, &y, &z, !ss->button_down_p);
      glTranslatef (0, 0, z/2);
    }

  /* Compute the "drop in and out" state.
   */
  switch (frame->mode)
    {
    case EARLY:
      abort();
      break;
    case NORMAL:
      if (!ss->button_down_p &&
          now >= frame->expires &&
          ss->loads_in_progress == 0)  /* only load one at a time */
        load_image (mi, frame);
      break;
    case LOADING:
      break;
    case OUT:
      if (--frame->mode_tick <= 0) {
        image swap = frame->current;
        frame->current = frame->loading;
        frame->loading = swap;

        frame->mode = IN;
        frame->mode_tick = fade_ticks / speed;
      }
      break;
    case IN:
      if (--frame->mode_tick <= 0)
        frame->mode = NORMAL;
      break;
    default:
      abort();
    }

  /* Now translate for current in/out state.
   */
  if (frame->mode == OUT || frame->mode == IN)
    {
      GLfloat t = (frame->mode == OUT
                   ? frame->mode_tick / (fade_ticks / speed)
                   : (((fade_ticks / speed) - frame->mode_tick + 1) /
                      (fade_ticks / speed)));
      t = 5 * (1 - t);
      if (frame->from_top_p) t = -t;
      glTranslatef (0, t, 0);
    }

  if (body_p)					/* Draw the image quad. */
    {
      if (! wire)
        {
          glColor3f (1, 1, 1);
          glNormal3f (0, 0, 1);
          glEnable (GL_TEXTURE_2D);
          glBegin (GL_QUADS);
          glNormal3f (0, 0, 1);
          glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
          glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
          glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
          glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
          glEnd();
        }

      /* Draw a box around it.
       */
      glLineWidth (2.0);
      glColor3f (0.5, 0.5, 0.5);
      glDisable (GL_TEXTURE_2D);
      glBegin (GL_LINE_LOOP);
      glVertex3f (0, 0, 0);
      glVertex3f (1, 0, 0);
      glVertex3f (1, aspect, 0);
      glVertex3f (0, aspect, 0);
      glEnd();

    }
  else					/* Draw a title under the image. */
    {
      XCharStruct e;
      int sw, sh;
      GLfloat scale = 0.05;
      char *title = frame->current.title ? frame->current.title : "(untitled)";
      texture_string_metrics (ss->texfont, title, &e, 0, 0);
      sw = e.width;
      sh = e.ascent + e.descent;

      glTranslatef (0, -scale, 0);

      scale /= sh;
      glScalef (scale, scale, scale);

      glTranslatef (((1/scale) - sw) / 2, 0, 0);
      glColor3f (1, 1, 1);

      if (!wire)
        {
          glEnable (GL_TEXTURE_2D);
          print_texture_string (ss->texfont, title);
        }
      else
        {
          glBegin (GL_LINE_LOOP);
          glVertex3f (0,  0,  0);
          glVertex3f (sw, 0,  0);
          glVertex3f (sw, sh, 0);
          glVertex3f (0,  sh, 0);
          glEnd();
        }
    }

  glPopMatrix();
}
示例#9
0
static void
loading_msg (ModeInfo *mi, int n)
{
  carousel_state *ss = &sss[MI_SCREEN(mi)];
  int wire = MI_IS_WIREFRAME(mi);
  char text[100];

  if (wire) return;

  if (n == 0)
    sprintf (text, "Loading images...");
  else
    sprintf (text, "Loading images...  (%d%%)",
             (int) (n * 100 / MI_COUNT(mi)));

  if (ss->loading_sw == 0)
    {
      /* only do this once, so that the string doesn't move. */
      XCharStruct e;
      texture_string_metrics (ss->texfont, text, &e, 0, 0);
      ss->loading_sw = e.width;
      ss->loading_sh = e.ascent + e.descent;
    }

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

/*
  {
    double rot = current_device_rotation();
    glRotatef(rot, 0, 0, 1);
    if ((rot >  45 && rot <  135) ||
        (rot < -45 && rot > -135))
      {
        GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
        glScalef (s, 1/s, 1);
      }
  }
*/

# ifdef HAVE_MOBILE
  if (MI_WIDTH(mi) < MI_HEIGHT(mi))  /* portrait orientation */
    {
      GLfloat s = (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi));
      glScalef (s, s, s);
      glTranslatef(-s/2, 0, 0);
    }
# endif

  glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
  glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
                (MI_HEIGHT(mi) - ss->loading_sh) / 2,
                0);
  glColor3f (1, 1, 0);
  glEnable (GL_TEXTURE_2D);
  glDisable (GL_DEPTH_TEST);
  print_texture_string (ss->texfont, text);
  glEnable (GL_DEPTH_TEST);
  glPopMatrix();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);

  glFinish();
  glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
}
示例#10
0
ENTRYPOINT void 
init_fliptext (ModeInfo *mi)
{
  int wire = MI_IS_WIREFRAME(mi);

  fliptext_configuration *sc;

  MI_INIT(mi, scs);

  sc = &scs[MI_SCREEN(mi)];
  sc->lines = (line **) calloc (max_lines+1, sizeof(char *));

  sc->dpy = MI_DISPLAY(mi);

  if ((sc->glx_context = init_GL(mi)) != NULL) {
    reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
    clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
  }

  {
    XCharStruct e;
    int cw, lh, ascent, descent;
    sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
    check_gl_error ("loading font");
    texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
    cw = e.width;
    lh = ascent + descent;
    sc->char_width = cw;
    sc->line_height = lh;
  }

  if (!wire)
    {
      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
      glEnable (GL_BLEND);
      glEnable (GL_ALPHA_TEST);
      glEnable (GL_TEXTURE_2D);

      /* "Anistropic filtering helps for quadrilateral-angled textures.
         A sharper image is accomplished by interpolating and filtering
         multiple samples from one or more mipmaps to better approximate
         very distorted textures.  This is the next level of filtering
         after trilinear filtering." */
      if (strstr ((char *) glGetString(GL_EXTENSIONS),
                  "GL_EXT_texture_filter_anisotropic"))
      {
        GLfloat anisotropic = 0.0;
        glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
        if (anisotropic >= 1.0)
          glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
                           anisotropic);
      }
    }
  
  /* The default font is (by fiat) "18 points".
     Interpret the user's font size request relative to that.
   */
  sc->font_scale = 3 * (font_size / 18.0);

  if (target_columns <= 2) target_columns = 2;

  /* Figure out what the wrap column should be, in font-coordinate pixels.
     Compute it from the given -columns value, but don't let it be wider
     than the screen.
   */
  {
    GLfloat maxw = 110 * sc->line_height / sc->font_scale;  /* magic... */
    sc->font_wrap_pixels = target_columns * sc->char_width;
    if (sc->font_wrap_pixels > maxw ||
        sc->font_wrap_pixels <= 0)
      sc->font_wrap_pixels = maxw;
  }

  sc->buf_size = target_columns * max_lines;
  sc->buf = (char *) calloc (1, sc->buf_size);

  alignment_random_p = False;
  if (!alignment_str || !*alignment_str ||
      !strcasecmp(alignment_str, "left"))
    sc->alignment = -1;
  else if (!strcasecmp(alignment_str, "center") ||
           !strcasecmp(alignment_str, "middle"))
    sc->alignment = 0;
  else if (!strcasecmp(alignment_str, "right"))
    sc->alignment = 1;
  else if (!strcasecmp(alignment_str, "random"))
    sc->alignment = -1, alignment_random_p = True;

  else
    {
      fprintf (stderr,
               "%s: alignment must be left/center/right/random, not \"%s\"\n",
               progname, alignment_str);
      exit (1);
    }

  sc->tc = textclient_open (sc->dpy);

  if (max_lines < 1) max_lines = 1;
  min_lines = max_lines * 0.66;
  if (min_lines > max_lines - 3) min_lines = max_lines - 4;
  if (min_lines < 1) min_lines = 1;

  parse_color (mi, "foreground",
               get_string_resource(mi->dpy, "foreground", "Foreground"),
               sc->color);

  sc->top_margin = (sc->char_width * 100);
  sc->bottom_margin = -sc->top_margin;
  reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));  /* compute left/right */
}
示例#11
0
static void
draw_line (ModeInfo *mi, line *line)
{
  int wire = MI_IS_WIREFRAME(mi);
  fliptext_configuration *sc = &scs[MI_SCREEN(mi)];

  if (! line->text || !*line->text ||
      line->state == NEW || line->state == HESITATE || line->state == DEAD)
    return;

  glPushMatrix();
  glTranslatef (line->current.x, line->current.y, line->current.z);

  glRotatef (line->cth, 0, 1, 0);

  if (sc->alignment == 1)
    glTranslatef (-line->width, 0, 0);
  else if (sc->alignment == 0)
    glTranslatef (-line->width/2, 0, 0);

  glScalef (sc->font_scale, sc->font_scale, sc->font_scale);

  glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);

  if (!wire)
    print_texture_string (sc->texfont, line->text);
  else
    {
      int w, h;
      char *s = line->text;
      char c[2];
      c[1]=0;
      glDisable (GL_TEXTURE_2D);
      glColor3f (0.4, 0.4, 0.4);
      while (*s)
        {
          XCharStruct e;
          *c = *s++;
          texture_string_metrics (sc->texfont, c, &e, 0, 0);
          w = e.width;
          h = e.ascent + e.descent;
          glBegin (GL_LINE_LOOP);
          glVertex3f (0, 0, 0);
          glVertex3f (w, 0, 0);
          glVertex3f (w, h, 0);
          glVertex3f (0, h, 0);
          glEnd();
          glTranslatef (w, 0, 0);
        }
    }

#if 0
  glDisable (GL_TEXTURE_2D);
  glColor3f (0.4, 0.4, 0.4);
  glBegin (GL_LINE_LOOP);
  glVertex3f (0, 0, 0);
  glVertex3f (line->width/sc->font_scale, 0, 0);
  glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
  glVertex3f (0, line->height/sc->font_scale, 0);
  glEnd();
  if (!wire) glEnable (GL_TEXTURE_2D);
#endif

  glPopMatrix();

  mi->polygon_count += strlen (line->text);
}
示例#12
0
static void
draw_image (ModeInfo *mi, int i, GLfloat t, GLfloat s, GLfloat z)
{
  int wire = MI_IS_WIREFRAME(mi);
  photopile_state *ss = &sss[MI_SCREEN(mi)];
  image *frame = ss->frames + i;

  position pos = interpolate(t, frame->pos);
  GLfloat w = frame->geom.width * 0.5;
  GLfloat h = frame->geom.height * 0.5;
  GLfloat z1 = z - 0.25 / (MI_COUNT(mi) + 1);
  GLfloat z2 = z - 0.5  / (MI_COUNT(mi) + 1); 
  GLfloat w1 = w;
  GLfloat h1 = h;
  GLfloat h2 = h;

  if (polaroid_p)
    {
      GLfloat minSize = MIN(w, h);
      GLfloat maxSize = MAX(w, h);

      /* Clip or scale image to fit in the frame.
       */
      if (clip_p)
        {
          w = h = minSize;
        }
      else
        {
          GLfloat scale = minSize / maxSize;
          w *= scale;
          h *= scale;
        }

      w1 = minSize * 1.16;      /* enlarge frame border */
      h1 = minSize * 1.5;
      h2 = w1;
      s /= 1.5;                 /* compensate for border size */
    }

  glPushMatrix();

  /* Position and scale this image.
   */
  glTranslatef (pos.x, pos.y, 0);
  glRotatef (pos.angle, 0, 0, 1);
  glScalef (s, s, 1);

  /* Draw the drop shadow. */
  if (shadows_p && !wire)
    {
      glColor3f (0, 0, 0);
      draw_drop_shadow(ss->shadow, -w1, -h1, z2, 2.0 * w1, h1 + h2, 20.0);
      glDisable (GL_BLEND);
    }

  glDisable (GL_LIGHTING);
  glEnable (GL_DEPTH_TEST);
  glDisable (GL_CULL_FACE);

  /* Draw the retro instant-film frame.
   */
  if (polaroid_p)
    {
      if (! wire)
        {
          glShadeModel (GL_SMOOTH);
          glEnable (GL_LINE_SMOOTH);
          glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);

          glColor3f (1, 1, 1);
          glBegin (GL_QUADS);
          glVertex3f (-w1, -h1, z2);
          glVertex3f ( w1, -h1, z2);
          glVertex3f ( w1,  h2, z2);
          glVertex3f (-w1,  h2, z2);
          glEnd();
        }

      glLineWidth (1.0);
      glColor3f (0.5, 0.5, 0.5);
      glBegin (GL_LINE_LOOP);
      glVertex3f (-w1, -h1, z);
      glVertex3f ( w1, -h1, z);
      glVertex3f ( w1,  h2, z);
      glVertex3f (-w1,  h2, z);
      glEnd();
    }

  /* Draw the image quad.
   */
  if (! wire)
    {
      GLfloat texw = w / frame->tw;
      GLfloat texh = h / frame->th;
      GLfloat texx = (frame->geom.x + 0.5 * frame->geom.width)  / frame->tw;
      GLfloat texy = (frame->geom.y + 0.5 * frame->geom.height) / frame->th;

      glBindTexture (GL_TEXTURE_2D, frame->texid);
      glEnable (GL_TEXTURE_2D);
      glColor3f (1, 1, 1);
      glBegin (GL_QUADS);
      glTexCoord2f (texx - texw, texy + texh); glVertex3f (-w, -h, z1);
      glTexCoord2f (texx + texw, texy + texh); glVertex3f ( w, -h, z1);
      glTexCoord2f (texx + texw, texy - texh); glVertex3f ( w,  h, z1);
      glTexCoord2f (texx - texw, texy - texh); glVertex3f (-w,  h, z1);
      glEnd();
      glDisable (GL_TEXTURE_2D);
    }

  /* Draw a box around it.
   */
  if (! wire)
    {
      glShadeModel (GL_SMOOTH);
      glEnable (GL_LINE_SMOOTH);
      glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
    }
  glLineWidth (1.0);
  glColor3f (0.5, 0.5, 0.5);
  glBegin (GL_LINE_LOOP);
  glVertex3f (-w, -h, z);
  glVertex3f ( w, -h, z);
  glVertex3f ( w,  h, z);
  glVertex3f (-w,  h, z);
  glEnd();

  /* Draw a title under the image.
   */
  if (titles_p)
    {
      int sw, sh, ascent, descent;
      GLfloat scale = 1;
      const char *title = frame->title ? frame->title : "(untitled)";
      XCharStruct e;

      /* #### Highly approximate, but doing real clipping is harder... */
      int max = 35;
      if (strlen(title) > max)
        title += strlen(title) - max;

      texture_string_metrics (ss->texfont, title, &e, &ascent, &descent);
      sw = e.width;
      sh = ascent + descent;

      /* Scale the text to match the pixel size of the photo */
      scale *= w / 300.0;

      /* Move to below photo */
      glTranslatef (0, -h - sh * (polaroid_p ? 2.2 : 0.5), 0);
      glTranslatef (-sw*scale/2, sh*scale/2, z);
      glScalef (scale, scale, 1);

      if (wire || !polaroid_p)
        {
          glColor3f (1, 1, 1);
        }
      else
        {
          glColor3f (0, 0, 0);
        }

      if (!wire)
        {
          glEnable (GL_TEXTURE_2D);
          glEnable (GL_BLEND);
          print_texture_string (ss->texfont, title);
        }
      else
        {
          glBegin (GL_LINE_LOOP);
          glVertex3f (0,  0,  0);
          glVertex3f (sw, 0,  0);
          glVertex3f (sw, sh, 0);
          glVertex3f (0,  sh, 0);
          glEnd();
        }
    }

  glPopMatrix();
}