aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pnmtilesplit/Makefile19
-rw-r--r--pnmtilesplit/pnmtilesplit.c250
2 files changed, 269 insertions, 0 deletions
diff --git a/pnmtilesplit/Makefile b/pnmtilesplit/Makefile
new file mode 100644
index 000000000..be40c2642
--- /dev/null
+++ b/pnmtilesplit/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile:
+# Build pnmtilesplit.
+#
+# Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+# Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+#
+# $Id: Makefile,v 1.1 2006-09-13 18:40:29 chris Exp $
+#
+
+CFLAGS = -Wall -g
+LDFLAGS =
+LDLIBS = -lnetpbm # is this a debianism?
+
+pnmtilesplit: pnmtilesplit.c
+ $(CC) $(CFLAGS) pnmtilesplit.c $(LDFLAGS) $(LDLIBS) -o pnmtilesplit
+
+clean:
+ rm -f pnmtilesplit *~ core
diff --git a/pnmtilesplit/pnmtilesplit.c b/pnmtilesplit/pnmtilesplit.c
new file mode 100644
index 000000000..f7820d31d
--- /dev/null
+++ b/pnmtilesplit/pnmtilesplit.c
@@ -0,0 +1,250 @@
+/*
+ * pnmtilesplit.c:
+ * Split a single large PNM file into numerous smaller tiles.
+ *
+ * Copyright (c) 2005 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ */
+
+static const char rcsid[] = "$Id: pnmtilesplit.c,v 1.1 2006-09-13 18:40:29 chris Exp $";
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pam.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+
+#define err(...) do { fprintf(stderr, "pnmtilesplit: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
+#define die(...) do { err(__VA_ARGS__); exit(1); } while (0)
+
+/* open_output_file FORMAT PIPE I J [PID]
+ * Open a new output file, constructing it from FORMAT and the column- and
+ * row-index values I and J. If PIPE is non-NULL, open the file via a pipe
+ * through the shell. Returns a stdio file handle on success or abort on
+ * failure. If PIPE is non-NULL then the process ID of the child process is
+ * saved in *PID. */
+FILE *open_output_file(const char *fmt, const char *pipe_via,
+ const int i, const int j, pid_t *child_pid) {
+ FILE *fp;
+ char *filename;
+ filename = malloc(strlen(fmt) + 64);
+ sprintf(filename, fmt, i, j);
+ /* XXX consider creating directories if they don't already exist? */
+ if (pipe_via) {
+ pid_t p;
+ int fd, pp[2];
+ if (-1 == (fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)))
+ die("%s: open: %s", filename, strerror(errno));
+ else if (-1 == pipe(pp))
+ die("pipe: %s", strerror(errno));
+ else if (!(fp = fdopen(pp[1], "w")))
+ die("fdopen: %s", strerror(errno));
+
+ if (-1 == (p = fork()))
+ die("fork: %s", strerror(errno));
+ else if (0 == p) {
+ /* run the pipe command via /bin/sh */
+ char *argv[4] = {"/bin/sh", "-c", 0};
+ close(0);
+ close(1);
+ close(pp[1]);
+ dup(pp[0]); /* standard input */
+ close(pp[0]);
+ dup(fd); /* standard output */
+ close(fd);
+ argv[2] = (char*)pipe_via;
+ execve("/bin/sh", argv, NULL);
+ err("%s: %s", pipe_via, strerror(errno));
+ _exit(1);
+ } else if (child_pid)
+ *child_pid = p;
+ } else if (!(fp = fopen(filename, "w")))
+ die("%s: open: %s", filename, strerror(errno));
+
+ free(filename);
+
+ return fp;
+}
+
+/* usage STREAM
+ * Write a usage message to STREAM. */
+void usage(FILE *fp) {
+ fprintf(fp,
+"pnmtilesplit - split a PNM file into fixed-size tiles\n"
+"\n"
+"Usage: pnmtilesplit -h | [OPTIONS] WIDTH HEIGHT [INPUT]\n"
+"\n"
+"Split the INPUT image, or, if it is not specified, the image on standard\n"
+"input, into WIDTH-by-HEIGHT pixel tiles. If WIDTH or HEIGHT do not divide\n"
+"the dimensions of the input image exactly, a warning will be printed and\n"
+"the pixels at the extreme right and bottom of the input image will be\n"
+"discarded.\n"
+"\n"
+"Options:\n"
+"\n"
+" -h Display this help message on standard output\n"
+"\n"
+" -f FORMAT Use the printf-style FORMAT for the name of the output file,\n"
+" instead of \"%%d,%%d.pnm\".\n"
+"\n"
+" -p COMMAND Don't write files directly, but pipe them via COMMAND. The\n"
+" COMMAND is interpreted by the shell.\n"
+"\n"
+"Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.\n"
+"Email: chris@mysociety.org; WWW: http://www.mysociety.org/\n"
+"%s\n",
+ rcsid);
+}
+
+/* main ARGC ARGV
+ * Entry point. */
+int main(int argc, char *argv[]) {
+ int tile_w, tile_h, cols, rows;
+ char *img_name;
+ FILE *img_fp, **tile_fp;
+ struct pam img_pam, *tile_pam;
+ pid_t *tile_pid;
+ char *outfile_format = "%d,%d.pnm", *pipe_via = NULL;
+ extern int opterr, optopt, optind;
+ static const char optstr[] = "hf:p:";
+ int i, j, c;
+ tuple *img_row, *tile_row = NULL;
+
+ pnm_init(&argc, argv);
+ opterr = 0;
+
+ while (-1 != (c = getopt(argc, argv, optstr))) {
+ switch (c) {
+ case 'h':
+ usage(stdout);
+ return 0;
+
+ case 'f':
+ outfile_format = optarg;
+ break;
+
+ case 'p':
+ pipe_via = optarg;
+ break;
+
+ case '?':
+ default:
+ if (strchr(optstr, optopt))
+ err("option -%c requires an argument", optopt);
+ else
+ err("unknown option -%c", optopt);
+ die("try -h for help");
+ }
+ }
+
+ if (argc - optind < 2 || argc - optind > 3) {
+ err("two or three non-option arguments required");
+ die("try -h for help");
+ }
+
+ if (0 == (tile_w = atoi(argv[optind])))
+ die("\"%s\" is not a valid tile width", argv[optind]);
+ else if (0 == (tile_h = atoi(argv[optind + 1])))
+ die("\"%s\" is not a valid tile height", argv[optind + 1]);
+
+ if (argv[optind + 2]) {
+ img_name = argv[optind + 2];
+ if (!(img_fp = fopen(img_name, "rb"))) {
+ die("%s: %s", img_name, strerror(errno));
+ return 1;
+ }
+ } else {
+ img_name = "(standard input)";
+ img_fp = stdin;
+ }
+
+ /* lamely, this will just abort if something goes wrong */
+ pnm_readpaminit(img_fp, &img_pam, sizeof img_pam);
+
+ /* couple of checks on the image dimensions */
+ if (tile_w > img_pam.width)
+ die("image width (%d) is smaller than tile width (%d)",
+ img_pam.width, tile_w);
+ else if (img_pam.width % tile_w) {
+ err("warning: tile width does not divide image width exactly");
+ err("last %d columns of image will not be included in any tile",
+ img_pam.width % tile_w);
+ }
+ cols = img_pam.width / tile_w;
+
+ if (tile_h > img_pam.height)
+ die("image height (%d) is smaller than tile height (%d)",
+ img_pam.height, tile_h);
+ else if (img_pam.height % tile_h) {
+ err("warning: tile height does not divide image height exactly");
+ err("warning: last %d rows of image will not be included in any tile",
+ img_pam.height % tile_h);
+ }
+ rows = img_pam.height / tile_h;
+
+ tile_fp = malloc(cols * sizeof *tile_fp);
+ tile_pam = malloc(cols * sizeof *tile_pam);
+ tile_pid = malloc(cols * sizeof *tile_pid);
+
+ if (!(img_row = pnm_allocpamrow(&img_pam)))
+ die("unable to allocate storage for input row");
+
+ for (j = 0; j < rows; ++j) {
+ int y;
+
+ /* Create output files. */
+ for (i = 0; i < cols; ++i) {
+ tile_pam[i] = img_pam;
+ tile_pam[i].file = tile_fp[i]
+ = open_output_file(outfile_format, pipe_via, i, j,
+ tile_pid + i);
+ tile_pam[i].width = tile_w;
+ tile_pam[i].height = tile_h;
+ pnm_writepaminit(tile_pam + i);
+
+ if (!tile_row && !(tile_row = pnm_allocpamrow(tile_pam + i)))
+ die("unable to allocate storage for output row");
+ }
+
+ /* Copy the image into the various tiles. */
+ for (y = 0; y < tile_h; ++y) {
+ int x;
+ pnm_readpamrow(&img_pam, img_row);
+ for (i = 0; i < cols; ++i) {
+ for (x = 0; x < tile_w; ++x)
+ tile_row[x] = img_row[x + i * tile_w];
+ pnm_writepamrow(tile_pam + i, tile_row);
+ fflush(tile_fp[i]);
+ }
+ }
+
+ /* Close the output files and check status. */
+ for (i = 0; i < cols; ++i) {
+ if (-1 == fclose(tile_fp[i]))
+ die("while writing tile (%d, %d): %s", i, j, strerror(errno));
+ if (pipe_via) {
+ /* Collect exit status of child process. */
+ pid_t p;
+ int st;
+ if (-1 == (p = waitpid(tile_pid[i], &st, 0)))
+ die("waitpid: %s", strerror(errno));
+ else if (st) {
+ if (WIFEXITED(st))
+ die("child process for tile (%d, %d) failed with "
+ "status %d", i, j, WEXITSTATUS(st));
+ else
+ die("child process for tile (%d, %d) killed by "
+ "signal %d", i, j, WTERMSIG(st));
+ }
+ }
+ }
+ }
+ return 0;
+}