September 2005

Etu: Enlightenment Thumbnailing Utility v0.1

The Enlightenment project has created a great deal of incredibly useful software as a result of working on a window manager. One piece of that software is the Epeg API. The Epeg software is designed solely to thumbnail - fast. In two other articles , single file thumbnailing and using a hardcoded source to destination directory method was used. In this text, the two are combined and some errors in the previous implementations fixed.

Note: For those who want to skip the diatribe, you can download the software.

Setting things up

There is a lot that goes on in etu, ironically it is mostly input parsing and directory/file operations. The beginning of the program has the includes and prototypes.

/* 
 * etu.c - Epeg Thumbnailing Utility. Thx to raster, devilhorns and icarus.
 */

#include <getopt.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <Epeg.h>

void update_thumb (char *, char *, int, int, int);
void create_thumb (char *, char *, int, int, int);
void usage        (void);
int check_handle  (char *);
char *fullpath    (char *, char *);
...

main()

Nothing fancy yet, now the main function along with the initialized variables.

...
int main (int argc, char **argv)
{
    int c;
    int thumb_height;
    int thumb_quality;
    int thumb_width;
    char *thumb_dir;  /* output dir to be processed  */
    char *jpegs_dir;  /* input dir to be processed   */
    char *jpeg_file;  /* input file to be processed  */
    char *thumb_file; /* output file to be processed */

    jpeg_file     = NULL;
    thumb_file    = NULL; 
    jpegs_dir     = NULL;
    thumb_dir     = NULL;
    thumb_height  = 96;
    thumb_quality = 75;
    thumb_width   = 128;
...

The c variable is just a counter. The rest is pretty obviously named. Note that the single file and directory names are not preset to anything like some of the earlier versions. It should be noted that the program only works in one of two modes, a single input regular image and output thumbnail or a single input directory full of images and an output directory of corresponding thumbnails.

Parsing Loop

Next, of course, is the parsing loop. The GNU getopt() bits are used for parsing input. The options fill in the NULL values which are either directory handles or filenames and/or overrides the dimensions variables.

    while ((c = getopt(argc, argv, "d:h:i:o:q:s:w:")) != -1) {
        switch (c) {
        case 'd':
            thumb_dir = optarg;
            break;
        case 'h':
            thumb_height = atoi(optarg);
            break;
        case 'i':
            jpeg_file = optarg;
            break;
        case 'o':
            thumb_file = optarg;
            break;
        case 'q':
            thumb_quality = atoi(optarg);
            break;
        case 's':
            jpegs_dir = optarg;
            break;
        case 'w':
             thumb_width = atoi(optarg);
            break;
        default:
            usage();
            return 1;
            break;
        }
    }

The rest of main()

The rest of the main function checks to see if there is an input file, if there is - just process it by calling create_thumb() and assume there is an output file, if there isn't nothing will happen. If there is not an input file it assumes that a check for a source directory of images is needed, provided that passes it checks to see if the destination directory exists - if it doesn't it creates the destination. If the destination does exist - the program simply updates it. etu could be used to continually refresh a given thumbnail directory. Once the directories are taken care of, the update_thumb() function is called.


    if (jpeg_file != NULL) {
        create_thumb(jpeg_file, thumb_file,
                thumb_height, thumb_quality, thumb_width);
        return 0;
    }

    if (check_handle(jpegs_dir) != 0) {
        fprintf(stderr, "%s directory not found\n", jpegs_dir);
        return 1;
    }

    if (check_handle(thumb_dir) != 0)
        if (mkdir(thumb_dir, 0755)) {
            fprintf(stderr,
                "Could not create directory %s\n", thumb_dir);
            return 1;
        }

    update_thumb(jpegs_dir, thumb_dir,
            thumb_height, thumb_quality, thumb_width);

    return 0;
}

Etu Specific Functions

Next there are two etu specific functions that are used to handle directory operations and thumbnail creation.

update_thumb()

Update is somewhat of a misnomer, basically a trick is used in etu, even if the thumbnail directory is not there already it checks and if there is not a corresponding thumbnail, it creates one. Of course, if there is one, it keeps going. Basically all that happens is the source jpegs directory is opened on a handle and the thumbnail (of the same name) is created in the thumbnail directory. Note that in earlier versions, the DIR * handle was passed around.

void update_thumb(char *jpegs, char *thumbs, 
                   int height, int quality, int width)
{
    int i;
    char thumb_path[PATH_MAX];
    char jpeg_path[PATH_MAX];
    struct dirent *jpegs_dp;
    DIR * jpegs_dir_handle;

    jpegs_dir_handle = opendir(jpegs);

    i = 0;

    while (jpegs_dp = (readdir(jpegs_dir_handle))) {
        if (i > 1) {
            strncpy(thumb_path, fullpath(jpegs_dp->d_name, thumbs),
                sizeof(jpegs_dp->d_name) + 1);

                if (check_handle(thumb_path) != 0) {
                strncpy(jpeg_path,
                    fullpath(jpegs_dp->d_name, jpegs),
                    sizeof(jpegs_dp->d_name) + 1);

                create_thumb(jpeg_path, thumb_path,
                        height, quality, width);

            }
        }

        i++;
    }

    closedir(jpegs_dir_handle);
}

create_thumb()

In the thumb creation code, the actual Epeg calls are made using the full paths and dimension values from update_thumb().

void create_thumb(char *jpeg, char *thumb, 
                   int height, int quality, int width)
{
    Epeg_Image * jpeg_image;

    jpeg_image = epeg_file_open(jpeg);

    if (!jpeg_image) {
        fprintf(stderr, "Cannot open %s\n", jpeg);
        exit (1);
    }

    epeg_decode_size_set(jpeg_image, width, height);
    epeg_quality_set(jpeg_image, quality);
    epeg_file_output_set(jpeg_image, thumb);
    epeg_encode(jpeg_image);
    epeg_close(jpeg_image);
}

The Epeg calls do as follows in order:

  1. Set the filename and dimensions.
  2. Set the quality level.
  3. Setup the destination.
  4. Encode the thumbnail.
  5. Close out the file handle.

Helpers

The program has two helpers, one that checks to see if a directory exists (by looking the same way as if checking a file) and another that builds a path. They are both pretty straightforward.

int check_handle(char *dir)
{
    struct stat statbuf;

    if (stat(dir, &statbuf) != 0)
        return 1;

    return 0;
}

char *fullpath(char *file, char *dir)
{
    static char path[PATH_MAX];

    strcpy(path, dir);
    strcat(path, "/");
    strcat(path, file);

    return (path);
}

There is also a usage() function, however it does not need to be included here.

Example Usage

A quick example, of course, is always nice. In the following there are two scenarios, first, a single input jpeg and an output thumb, then a directory of large jpegs to be processed.

[mui@vela:$] etu -i big_room.jpg -o big_room_thumb.jpg
[mui@vela:$] etu -s my_sic_party/ -d my_sic_party_thumbs

Summary & Thoughts

In earlier versions, some includes were there - but not used. The PATH_MAX variable comes to mind. Additionally, the simplest approach to handling files was used versus repeated DIR * handles and iterations.

The etu program has a lot of possibilities such as using it as is for building and maintaining thumbnails to extending it for wholesale management of images and their corresponding thumbnails. In any case, it does what it is supposed to - fast.

Previous: Epeg II