src/pngtile/main.c
author Tero Marttila <terom@fixme.fi>
Mon, 25 Jan 2010 22:04:02 +0200
changeset 101 578d90d0cf92
parent 98 f195b8195b5a
child 102 a4d1e046ed3e
permissions -rw-r--r--
refactor main.c into do_tile, add --benchmark, --randomize
#include "shared/log.h"

#include "pngtile.h"

#include <getopt.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>

enum option_names {

    _OPT_LONGONLY       = 255,

    OPT_BENCHMARK,
    OPT_RANDOMIZE,
};

/**
 * Command-line options
 */
static const struct option options[] = {
    { "help",           false,  NULL,   'h' },
    { "quiet",          false,  NULL,   'q' },
    { "verbose",        false,  NULL,   'v' },
    { "debug",          false,  NULL,   'D' },
    { "force-update",   false,  NULL,   'U' },
    { "no-update",      false,  NULL,   'N' },
    { "background",     true,   NULL,   'B' },
    { "width",          true,   NULL,   'W' },
    { "height",         true,   NULL,   'H' },
    { "x",              true,   NULL,   'x' },
    { "y",              true,   NULL,   'y' },
    { "zoom",           true,   NULL,   'z' },
    { "out",            true,   NULL,   'o' },
    { "threads",        true,   NULL,   'j' },
    
    // --long-only options
    { "benchmark",      true,   NULL,   OPT_BENCHMARK   },
    { "randomize",      false,  NULL,   OPT_RANDOMIZE   },
    { 0,                0,      0,      0               }
};

/**
 * Print usage/help info on stderr
 */
void help (const char *argv0)
{
    fprintf(stderr, "Usage: %s [options] <image> [...]\n", argv0);
    fprintf(stderr,
        "Open each of the given image files, check cache status, optionally update their cache, display image info, and\n"
        "optionally render a tile of each.\n"
        "\n"
        "\t-h, --help               show this help and exit\n"
        "\t-q, --quiet              supress informational output\n"
        "\t-v, --verbose            display more informational output\n"
        "\t-D, --debug              equivalent to -v\n"
        "\t-U, --force-update       unconditionally update image caches\n"
        "\t-N, --no-update          do not update the image cache\n"
        "\t-B, --background         set background pattern for sparse cache file: 0xHH..\n"
        "\t-W, --width      PX      set tile width\n"
        "\t-H, --height     PX      set tile height\n"
        "\t-x, --x          PX      set tile x offset\n"
        "\t-y, --y          PX      set tile y offset\n"
        "\t-z, --zoom       ZL      set zoom factor (<0)\n"
        "\t-o, --out        FILE    set tile output file\n"
        "\t-j, --threads    N       number of threads\n"
        "\t--benchmark      N       do N tile renders\n"
        "\t--randomize              randomize tile x/y coords\n"
    );
}

unsigned long parse_uint (const char *val, const char *name)
{
    char *endptr;
    long int out;

    // decode
    out = strtol(val, &endptr, 0);

    // validate
    if (*endptr || out < 0)
        EXIT_ERROR(EXIT_FAILURE, "Invalid value for %s: %s", name, val);

    // ok
    return out;
}

signed long parse_sint (const char *val, const char *name)
{
    char *endptr;
    long int out;

    // decode
    out = strtol(val, &endptr, 0);

    // validate
    if (*endptr)
        EXIT_ERROR(EXIT_FAILURE, "Invalid value for %s: %s", name, val);

    // ok
    return out;
}

long randrange (long start, long end)
{
    return start + (rand() * (end - start) / RAND_MAX);
}

/**
 * Randomize tile x/y with given image info
 */
void randomize_tile (struct pt_tile_info *ti, const struct pt_image_info *info)
{
    ti->x = randrange(0, info->img_width - ti->width);
    ti->y = randrange(0, info->img_height - ti->height);
}

/**
 * Render a tile
 */
int do_tile (struct pt_ctx *ctx, struct pt_image *image, const struct pt_tile_info *ti, const char *out_path)
{
    FILE *out_file = NULL;
    char tmp_name[] = "pt-tile-XXXXXX";
    int err = 0;
    
    if (!out_path) {
        int fd;
        
        // temporary file for output
        if ((fd = mkstemp(tmp_name)) < 0) {
            log_errno("mkstemp");
            goto error;
        }

        out_path = tmp_name;

        // open out
        if ((out_file = fdopen(fd, "wb")) == NULL) {
            log_errno("fdopen");
            goto error;
        }

    } else if (strcmp(out_path, "-") == 0) {
        // use stdout
        if ((out_file = fdopen(STDOUT_FILENO, "wb")) == NULL) {
            log_errno("fdopen: STDOUT_FILENO");
            goto error;
        }

    } else {
        // use file
        if ((out_file = fopen(out_path, "wb")) == NULL) {
            log_errno("fopen: %s", out_path);
            goto error;
        }

    }

    
    if (ctx) {
        // render
        log_info("\tAsync render tile %zux%zu@(%zu,%zu) -> %s", ti->width, ti->height, ti->x, ti->y, out_path);

        if ((err = pt_image_tile_async(image, ti, out_file))) {
            log_errno("pt_image_tile_async: %s", pt_strerror(err));
            goto error;
        }

        // will close it itself
        out_file = NULL;

    } else {
        // render
        log_info("\tRender tile %zux%zu@(%zu,%zu) -> %s", ti->width, ti->height, ti->x, ti->y, out_path);

        if ((err = pt_image_tile_file(image, ti, out_file))) {
            log_errno("pt_image_tile_file: %s", pt_strerror(err));
            goto error;
        }
    }

error:
    // cleanup
    if (out_file && fclose(out_file))
        log_warn_errno("fclose: out_file");

    return err;
}

int main (int argc, char **argv)
{
    int opt;
    bool force_update = false, no_update = false, randomize = false;
    struct pt_tile_info ti = {
        .width  = 800,
        .height = 600,
        .x      = 0,
        .y      = 0,
        .zoom   = 0
    };
    struct pt_image_params update_params = { };
    const char *out_path = NULL;
    int threads = 0, benchmark = 0;
    int err;
    
    // parse arguments
    while ((opt = getopt_long(argc, argv, "hqvDUNB:W:H:x:y:z:o:j:", options, NULL)) != -1) {
        switch (opt) {
            case 'h':
                // display help
                help(argv[0]);
                
                return EXIT_SUCCESS;

            case 'q':
                // supress excess log output
                set_log_level(LOG_WARN); break;

            case 'v':
            case 'D':
                // display additional output
                set_log_level(LOG_DEBUG); break;
            
            case 'U':
                // force update of image caches
                force_update = true; break;
            
            case 'N':
                // supress update of image caches
                no_update = true; break;

            case 'B':
                // background pattern
                {
                    unsigned int b1 = 0, b2 = 0, b3 = 0, b4 = 0;
                    
                    // parse 0xXXXXXXXX
                    if (sscanf(optarg, "0x%02x%02x%02x%02x", &b1, &b2, &b3, &b4) < 1)
                        FATAL("Invalid hex value for -B/--background: %s", optarg);
                    
                    // store
                    update_params.background_color[0] = b1;
                    update_params.background_color[1] = b2;
                    update_params.background_color[2] = b3;
                    update_params.background_color[3] = b4;

                } break;

            case 'W':
                ti.width = parse_uint(optarg, "--width"); break;

            case 'H':
                ti.height = parse_uint(optarg, "--height"); break;

            case 'x':
                ti.x = parse_uint(optarg, "--x"); break;

            case 'y':
                ti.y = parse_uint(optarg, "--y"); break;

            case 'z':
                ti.zoom = parse_sint(optarg, "--zoom"); break;

            case 'o':
                // output file
                out_path = optarg; break;

            case 'j':
                threads = parse_uint(optarg, "--threads"); break;

            case OPT_BENCHMARK:
                benchmark = parse_uint(optarg, "--benchmark"); break;
            
            case OPT_RANDOMIZE:
                randomize = true; break;

            case '?':
                // useage error
                help(argv[0]);

                return EXIT_FAILURE;

            default:
                // getopt???
                FATAL("getopt_long returned unknown code %d", opt);
        }
    }
    
    // end-of-arguments?
    if (!argv[optind])
        EXIT_WARN(EXIT_FAILURE, "No images given");
    


    struct pt_ctx *ctx = NULL;
    struct pt_image *image = NULL;
    enum pt_cache_status status;
    
    if (threads) {
        // build ctx
        log_debug("Construct pt_ctx with %d threads", threads);

        if ((err = pt_ctx_new(&ctx, threads)))
            EXIT_ERROR(EXIT_FAILURE, "pt_ctx_new: threads=%d", threads);
    }
    
    // process each image in turn
    log_debug("Processing %d images...", argc - optind);

    for (int i = optind; i < argc; i++) {
        const char *img_path = argv[i];

        log_debug("Loading image from: %s...", img_path);

        // open
        if ((err = pt_image_open(&image, ctx, img_path, PT_OPEN_UPDATE))) {
            log_errno("pt_image_open: %s: %s", img_path, pt_strerror(err));
            continue;
        }

        log_info("Opened image at: %s", img_path);
        
        // check if stale
        if ((status = pt_image_status(image)) < 0) {
            log_errno("pt_image_status: %s: %s", img_path, pt_strerror(status));
            goto error;
        }
        
        // update if stale
        if (status != PT_CACHE_FRESH || force_update) {
            if (status == PT_CACHE_NONE)
                log_debug("\tImage cache is missing");

            else if (status == PT_CACHE_STALE)
                log_debug("\tImage cache is stale");
            
            else if (status == PT_CACHE_INCOMPAT)
                log_debug("\tImage cache is incompatible");

            else if (status == PT_CACHE_FRESH)
                log_debug("\tImage cache is fresh");

            else
                log_warn("\tImage cache status is unknown");
            
            if (!no_update) {
                log_debug("\tUpdating image cache...");

                if ((err = pt_image_update(image, &update_params))) {
                    log_warn_errno("pt_image_update: %s: %s", img_path, pt_strerror(err));
                }

                log_info("\tUpdated image cache");

            } else {
                log_warn("\tSupressing cache update");
            }

        } else {    
            log_debug("\tImage cache is fresh");

            // ensure it's loaded
            log_debug("\tLoad image cache...");

            if ((err = pt_image_load(image)))
                log_errno("pt_image_load: %s", pt_strerror(err));

        }

        // show info
        const struct pt_image_info *info;
        
        if ((err = pt_image_info(image, &info))) {
            log_warn_errno("pt_image_info: %s: %s", img_path, pt_strerror(err));

        } else {
            log_info("\tImage dimensions: %zux%zu (%zu bpp)", info->img_width, info->img_height, info->img_bpp);
            log_info("\tImage mtime=%ld, bytes=%zu", (long) info->image_mtime, info->image_bytes);
            log_info("\tCache mtime=%ld, bytes=%zu, blocks=%zu (%zu bytes), version=%d", 
                    (long) info->cache_mtime, info->cache_bytes, info->cache_blocks, info->cache_blocks * 512, info->cache_version
            );
        }

        // render tile?
        if (benchmark) {
            log_info("\tRunning %d %stile renders...", benchmark, randomize ? "randomized " : "");

            // n times
            for (int i = 0; i < benchmark; i++) {
                // randomize x, y
                if (randomize)
                    randomize_tile(&ti, info);

                if (do_tile(ctx, image, &ti, out_path))
                    goto error;
            }

        } else if (out_path) {
            // randomize x, y
            if (randomize)
                randomize_tile(&ti, info);

            // just once
            if (do_tile(ctx, image, &ti, out_path))
                goto error;

        }
error:
        // cleanup
        // XXX: leak because of async
        if (!ctx)
            pt_image_destroy(image);
    }
    
    if (ctx) {
        log_info("Waiting for images to finish...");

        // wait for tile operations to finish...
        pt_ctx_shutdown(ctx);
    }

    log_info("Done");

    return 0;
}