Nominal Animal |
01-23-2012 07:53 PM |
Just for fun, I created a helper program that takes a command as an argument, and a stream of concatenated image files from standard input. It reads the next image, then fork()s and exec()s the desired command, supplying the image data to it via its standard input. After the command exits, the helper repeats the same for the next image.
This program is very crude. I wrote the format parsers from the specs, not by trial-and-error, so the logic should be on a sound basis, barring any typos and thinkos, of course. Consider it only a working prototype. I posted it in case somebody would like to (rewrite and) develop it further. I consider this code CC0 and/or public domain, whichever is applicable in your location.
The GIF and PNG parsers should be okay. GIF and PNG files have very straightforward structures, and are easy to parse and walk through. The JPEG parser I'm not absolutely sure of, although it does seem to work on all images I can throw at it. It does also tell you if it skips data it does not feed to the viewer.
It is my understanding that according to ITU-T.81, in the JPEG scan data, all 0xFFs should be stuffed (followed by a zero or one), or they indicate an RST marker (followed one byte between 0xD0 and 0xD7 inclusive), or they indicate the end of the scan data (being a normal JPEG marker). The parser exploits this, only looking at the 0xFF and following bytes in scan data. For all the non-scan data, it of course relies on the marker structure.
Here it is, per-image.c:
Code:
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#define EXIT_READ 99
#define EXIT_MEMORY 98
#define EXIT_PIPE 97
#define EXIT_FORK 96
#define EXIT_EXEC 95
#define EXIT_SIGNAL 94
#define EXIT_INPUT 93
/* Input buffer.
*/
static unsigned char *input_data = NULL; /* Input buffer */
static size_t input_have = 0; /* Number of bytes available */
static size_t input_size = 0; /* Number of bytes allocated */
/* Command to execute.
*/
static const char *command_file = NULL;
static const char *const *command_args = NULL;
/* Helper function to avoid using stdio: Write to standard error.
*/
static int wrerr(const void *const ptr, const size_t len)
{
const int descriptor = STDERR_FILENO;
const char *const q = (const char *)ptr + len;
const char *p = (const char *)ptr;
ssize_t n;
while (p < q) {
n = write(descriptor, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
return EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
static inline int wrerrs(const char *const string)
{
return (string && *string) ? wrerr(string, strlen(string)) : 0;
}
/* Output a fatal error message, and exit.
*/
static void fatal(const int exitstatus, const int num_strings, ...)
{
const char *s;
int i;
va_list a;
va_start(a, num_strings);
for (i = 0; i < num_strings; i++) {
s = va_arg(a, const char *);
if (s && *s)
if (wrerr(s, strlen(s)))
break;
}
va_end(a);
exit(exitstatus);
}
/* Helper to encode one signed integer to fatal error messages.
*/
static const char *errint(const int value)
{
static unsigned char buffer[4 + 4*sizeof(int)];
unsigned char *q = buffer + sizeof(buffer);
unsigned int u = (value < 0) ? (unsigned int)(-value) : value;
*(--q) = '\0';
*(--q) = '0' + (u % 10U); u /= 10U;
while (u) {
*(--q) = '0' + (u % 10U);
u /= 10U;
}
if (value < 0)
*(--q) = '-';
return (const char *)q;
}
/* Close a file descriptor without affecting errno.
*/
static inline int closefd(const int fd)
{
int saved_errno, result;
saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Move a file descriptor without affecting errno.
*/
static inline int dupfd(const int oldfd, const int newfd)
{
int saved_errno, result;
if (oldfd == newfd)
return 0;
saved_errno = errno;
do {
result = dup2(oldfd, newfd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Fork and execute the command, supplying size bytes from the input buffer.
* Return values:
* 0: Success (or child detached)
* <0: Killed by signal -(return value)
* >0: Exit status from child process.
*/
static int run(const size_t size)
{
pid_t child, pid;
int pipefd[2];
int result;
do {
result = pipe(pipefd);
} while (result == -1 && errno == EINTR);
if (result == -1)
fatal(EXIT_PIPE, 3, "Cannot create a pipe: ", strerror(errno), ".\n");
child = fork();
if (child == (pid_t)-1)
fatal(EXIT_FORK, 3, "Cannot fork: ", strerror(errno), ".\n");
if (!child) {
/* Child process. */
closefd(STDIN_FILENO);
if (dupfd(pipefd[0], STDIN_FILENO))
exit(EXIT_PIPE);
if (closefd(pipefd[0]) ||
closefd(pipefd[1]))
exit(EXIT_PIPE);
execvp(command_file, (char *const *)command_args);
exit(EXIT_EXEC);
}
/* Parent process. */
closefd(pipefd[0]);
/* Write the data to the write end of the pipe. */
{ const char *p = (char *)input_data;
const char *const q = (char *)input_data + size;
ssize_t n;
while (p < q) {
n = write(pipefd[1], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
closefd(pipefd[1]);
}
/* Wait for the child process to exit. */
do {
pid = waitpid(child, &result, 0);
} while (pid == (pid_t)-1 && errno == EINTR);
/* If the child detached, assume success. */
if (pid == (pid_t)-1)
return 0;
if (WIFEXITED(result))
return WEXITSTATUS(result) & 127;
if (WIFSIGNALED(result))
return -(WTERMSIG(result) & 127);
return 0;
}
/* Function to increase an allocation block.
*/
static inline size_t pad(const size_t n)
{
return (n | (size_t)8191) + (size_t)8129;
}
/* Read more input, until at least limit bytes buffered.
*/
static void need(const size_t limit)
{
const int descriptor = STDIN_FILENO;
ssize_t n;
while (input_have < limit) {
if (input_have >= input_size) {
const size_t size = pad(input_have);
unsigned char *data;
if (size < input_size)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
data = realloc(input_data, size);
if (!data)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
input_data = data;
input_size = size;
}
do {
n = read(descriptor, input_data + input_have, input_size - input_have);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n > (ssize_t)0)
input_have += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
fatal(EXIT_READ, 3, "Error reading standard input: ", strerror(errno), ".\n");
}
if (input_size < limit) {
const size_t size = pad(limit);
unsigned char *data;
if (size < input_size)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
data = realloc(input_data, size);
if (!data)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
input_data = data;
input_size = size;
}
if (input_have < limit)
memset(input_data + input_have, 0, input_size - input_have);
return;
}
/* Peek a byte in the input buffer.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned int peekb(const size_t offset)
{
if (offset >= input_have)
need(offset + 1);
return (unsigned int)(input_data[offset]);
}
/* Peek a 16-bit word in the input buffer. Offset is in bytes.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned int peekw(const size_t offset)
{
if (offset + 1 >= input_have)
need(offset + 2);
return (unsigned int)(input_data[offset + 0]) * 256U
+ (unsigned int)(input_data[offset + 1]);
}
/* Peek a 32-bit word in the input buffer. Offset is in bytes.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned long peekl(const size_t offset)
{
if (offset + 3 >= input_have)
need(offset + 4);
return (unsigned long)(input_data[offset + 0]) * 16777216UL
+ (unsigned long)(input_data[offset + 1]) * 65536UL
+ (unsigned long)(input_data[offset + 2]) * 256UL
+ (unsigned long)(input_data[offset + 3]);
}
/* Peek a number of bytes in the input buffer,
* returning zero if the data matches.
*/
static inline int peek(const size_t offset, const void *const data, const size_t bytes)
{
if (offset + bytes > input_have)
need(offset + bytes);
return memcmp(data, input_data + offset, bytes);
}
/* Discard bytes first bytes of input.
* Returns nonzero if there is further input available.
*/
static size_t discard(const size_t bytes)
{
if (bytes >= input_have)
need(bytes + 1);
if (bytes < input_have) {
memmove(input_data, input_data + bytes, input_have - bytes);
input_have = input_have - bytes;
} else
input_have = 0;
return input_have;
}
/*
* Image data parsers.
*
* Each of the is_format() functions will return 0 if the input buffer
* does not begin with an image of said format, and a nonzero length
* otherwise.
*/
/* GIF image parser.
*/
static size_t is_gif(void)
{
size_t length = 0, n;
int flags;
/* Verify GIF header */
if (peek(0, "GIF87a", 6) && peek(0, "GIF89a", 6))
return 0;
/* Skip Logical Screen Descriptor */
flags = peekb(10);
if (flags & 128)
length = 13 + (6 << (flags & 7));
else
length = 13;
/* Parse until end of data. */
while (1) {
switch (peekb(length)) {
case 0x3B: /* Trailer */
return length + 1;
case 0x21: /* Extension */
n = 1;
do {
length += n + 1;
n = peekb(length);
} while (n);
length++;
break;
case 0x2C: /* Image descriptor */
flags = peekb(length + 9);
if (flags & 128)
length += 10 + (6 << (flags & 7));
else
length += 10;
n = 0;
do {
length += n + 1;
n = peekb(length);
} while (n);
length++;
break;
default: /* Bad data. */
return 0;
}
}
}
/* PNG Image parser.
*/
static size_t is_png(void)
{
const unsigned char *p;
size_t i = 8;
size_t n;
if (peek(0, "\x89PNG\x0D\x0A\x1A\x0A", 8))
return 0;
while (1) {
n = peekl(i);
if (n > (size_t)0x7FFFFFFFUL || n < (size_t)0)
return 0;
peekb(i + n + (size_t)12);
p = (const unsigned char *)(input_data + i + 4);
if (!((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')))
return 0;
if (!((p[1] >= 'A' && p[1] <= 'Z') || (p[1] >= 'a' && p[1] <= 'z')))
return 0;
if (!((p[2] >= 'A' && p[2] <= 'Z') || (p[2] >= 'a' && p[2] <= 'z')))
return 0;
if (!((p[3] >= 'A' && p[3] <= 'Z') || (p[3] >= 'a' && p[3] <= 'z')))
return 0;
i += n + (size_t)12;
if (p[0] == 'I' && p[1] == 'E' && p[2] == 'N' && p[3] == 'D')
return i;
}
return 0;
}
/* JPEG image parser.
*/
static size_t is_jpeg(void)
{
size_t n, index = 0;
int b;
/* SOI (0xFF+ 0xD8) must be first in stream. */
b = peekb(index);
if (b != 0xFF)
return 0;
while (b == 0xFF)
b = peekb(++index);
if (b != 0xD8)
return 0;
index++;
while (1) {
/* Marker. */
b = peekb(index);
if (b != 0xFF)
return 0;
while (b == 0xFF)
b = peekb(++index);
/* Invalid? */
if (b < 0xC0)
return 0;
if (b >= 0xD0 && b <= 0xD7) {
/* RSTn? */
index++;
continue;
} else
if (b == 0xD8) {
/* Start of Image? */
index++;
continue;
} else
if (b == 0xD9) {
/* End of Image? */
return index + 1;
}
/* Marker has length indicator. */
n = peekw(++index);
if (n < (size_t)2)
return 0;
index += n;
/* Not Start of Scan? */
if (b != 0xDA)
continue;
/* Parse scan. */
do {
b = peekb(index);
while (b != 0xFF)
if (++index <= input_size)
b = peekb(index);
else
return 0;
while (b == 0xFF)
b = peekb(++index);
} while (b <= 0x01 || (b >= 0xD0 && b <= 0xD7));
index--;
continue;
}
}
/* Return the number of bytes that can be discarded. Conside
* discard(is_none());
* a replacement for
* while (!is_gif() && !is_png() && !is_jpg() && input_have)
* discard(1);
*/
size_t is_none(void)
{
size_t n = 0;
int b;
do {
b = peekb(n);
if (b == 'G')
if (peekb(n+1) == 'I' &&
peekb(n+2) == 'F' &&
peekb(n+3) == '8' &&
peekb(n+5) == 'a')
return n;
if (b == 0x89)
if (peekb(n+1) == 'P' &&
peekb(n+2) == 'N' &&
peekb(n+3) == 'G')
return n;
if (b == 0xFF)
if (peekb(n+1) == 0xD8)
return n;
} while (n++ <= input_have);
return input_have;
}
/*
* Main
*/
int main(int argc, char *argv[])
{
size_t len;
int retval;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
fatal(0, 5,
"\n"
"Usage: ", argv[0], " [ -h | --help ]\n"
" ", argv[0], " command [ args... ]\n"
"\n"
"The command will be run once for each image in standard input.\n"
"The command will receive the image data from standard input.\n"
"\n");
command_file = (const char *)argv[1];
command_args = (const char *const *)&(argv[1]);
/* Paranoid check: Verify the arg list ends with a NULL. */
if (argv[argc])
argv[argc] = NULL;
while (1)
if ((len = is_png()) ||
(len = is_gif()) ||
(len = is_jpeg()) ) {
/* Run the command. */
retval = run(len);
if (retval > 0)
fatal(retval, 5, "'", command_file, "': Nonzero (", errint(retval), ") exit status.\n");
if (retval < 0)
fatal(EXIT_SIGNAL, 5, "'", command_file, "': Died by signal (", errint(-retval), ").\n");
/* Discard image data. */
if (!discard(len))
return 0;
} else {
/* No data? */
if (!input_have)
return 0;
/* Skip unrecognized input. */
len = 0;
while (input_have && !is_png() && !is_gif() && !is_jpeg()) {
size_t const n = is_none();
discard(n);
len += n;
}
if (!input_have)
fatal(0, 3, "Discarded ", errint(len), " trailing garbage bytes.\n");
wrerrs("Skipped ");
wrerrs(errint(len));
wrerrs(" bytes of unrecognized input.\n");
continue;
}
}
Compile and install using
Code:
gcc per-image.c -Wall -O3 -s -o per-image
sudo install -m 0755 per-image /usr/local/bin/per-image
Run without parameters (or with just -h or --help) to see the usage. For example:
Code:
find / -name '*.jpg' -o -name '*.gif' -o -name '*.png' -exec cat '{}' ';' | per-image xli stdin
find / -name '*.jpg' -o -name '*.gif' -o -name '*.png' -exec cat '{}' ';' | per-image xview /dev/stdin
Hope you find this interesting,
|