https://doi.org/10.5201/ipol.2012.g-tvdc
Tip revision: 13cac45f201f2e0e4a336450abb835be0ddd9359 authored by Software Heritage on 12 June 2012, 00:00:00 UTC
ipol: Deposit 1194 in collection ipol
ipol: Deposit 1194 in collection ipol
Tip revision: 13cac45
cliio.c
/**
* @file cliio.c
* @brief Utilities for creating a command line interface
* @author Pascal Getreuer <getreuer@gmail.com>
*/
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cliio.h"
const image NullImage = {NULL, 0, 0, 0};
int AllocImageObj(image *f, int Width, int Height, int NumChannels)
{
if(!f)
return 0;
if(!(f->Data = (num *)malloc(sizeof(num)*
(((size_t)Width)*((size_t)Height))*NumChannels)))
{
*f = NullImage;
return 0;
}
f->Width = Width;
f->Height = Height;
f->NumChannels = NumChannels;
return 1;
}
void FreeImageObj(image f)
{
if(f.Data)
free(f.Data);
}
int ReadImageObj(image *f, const char *FileName)
{
if(!f || !(f->Data = (num *)ReadImage(&f->Width, &f->Height, FileName,
IMAGEIO_NUM | IMAGEIO_RGB | IMAGEIO_PLANAR)))
{
*f = NullImage;
return 0;
}
f->NumChannels = (IsGrayscale(f->Data, f->Width, f->Height)) ? 1:3;
return 1;
}
int ReadImageObjGrayscale(image *f, const char *FileName)
{
if(!f || !(f->Data = (num *)ReadImage(&f->Width, &f->Height, FileName,
IMAGEIO_NUM | IMAGEIO_GRAYSCALE | IMAGEIO_PLANAR)))
{
*f = NullImage;
return 0;
}
f->NumChannels = 1;
return 1;
}
int WriteImageObj(image f, const char *FileName, int JpegQuality)
{
if(!f.Data || !FileName)
return 0;
switch(f.NumChannels)
{
case 1:
return WriteImage(f.Data, f.Width, f.Height, FileName,
IMAGEIO_NUM | IMAGEIO_GRAYSCALE | IMAGEIO_PLANAR, JpegQuality);
case 3:
return WriteImage(f.Data, f.Width, f.Height, FileName,
IMAGEIO_NUM | IMAGEIO_RGB | IMAGEIO_PLANAR, JpegQuality);
case 4:
return WriteImage(f.Data, f.Width, f.Height, FileName,
IMAGEIO_NUM | IMAGEIO_RGBA | IMAGEIO_PLANAR, JpegQuality);
default:
return 0;
}
}
/** @brief Check whether all three channels in a color image are the same */
int IsGrayscale(num *Data, int Width, int Height)
{
const long NumPixels = ((long)Width) * ((long)Height);
const num *Red = Data;
const num *Green = Data + NumPixels;
const num *Blue = Data + 2*NumPixels;
long n;
for(n = 0; n < NumPixels; n++)
if(Red[n] != Green[n] || Red[n] != Blue[n])
return 0;
return 1;
}
/**
* @brief Extract a token from a null-terminated string
* @param Token destination buffer (with space for at least MaxLength+1 chars)
* @param Start pointer to the source string
* @param MaxLength maximum length of the token, not including null terminator
* @param Delim delimiter characters
* @return length of the token (greater than MaxLength indicates truncation).
*/
int GetStrToken(char *Token, const char *Start, int MaxLength, const char *Delim)
{
int NumChars = strcspn(Start, Delim);
int NumCopy = (NumChars <= MaxLength) ? NumChars : MaxLength;
strncpy(Token, Start, NumCopy);
Token[NumCopy] = '\0';
return NumChars;
}
/**
* @brief Read a floating-point number from a string
* @param Num is a pointer to where to store the result
* @param String is a pointer to the source string to parse
* @return the number of characters read (possibly 0).
*
* The function strtod does not seem to work on some systems. We use
* the following routine instead.
*
* The routine reads as many characters as possible to form a valid
* floating-point number in decimal or scientific notation. A return
* value of zero indicates that no valid number was found.
*/
int ParseDouble(double *Num, const char *String)
{
double Accum = 0, Div = 1, Exponent = 0;
int i = 0, Sign = 1, ExponentSign = 1;
char c;
if(!Num || !String)
return 0;
while(isspace(String[i])) /* Eat leading whitespace */
i++;
if(String[i] == '-') /* Read sign */
{
Sign = -1;
i++;
}
else if(String[i] == '+')
i++;
/* Read one or more digits appearing left of the decimal point */
if(isdigit(c = String[i]))
Accum = c - '0';
else
return 0; /* First character is not a digit */
while(isdigit(c = String[++i]))
Accum = 10*Accum + (c - '0');
if(c == '.') /* There is a decimal point */
{
/* Read zero or more digits appearing right of the decimal point */
while(isdigit(c = String[++i]))
{
Div *= 10;
Accum += (c - '0')/Div;
}
}
if(c == 'e' || c == 'E') /* There is an exponent */
{
i++;
if(String[i] == '-') /* Read exponent sign */
{
ExponentSign = -1;
i++;
}
else if(String[i] == '+')
i++;
/* Read digits in the exponent */
if(isdigit(c = String[i]))
{
Exponent = c - '0';
while(isdigit(c = String[++i]))
Exponent = 10*Exponent + (c - '0');
Exponent *= ExponentSign;
Accum = Accum * pow(10, Exponent);
}
}
Accum *= Sign;
*Num = Accum;
return i;
}
/**
* @brief Parse an arg list for param-value pairs
* @param Param where to store the parameter pointer
* @param Value where to store the value pointer
* @param TokenBuf token buffer with space for at least MaxLength+1 chars
* @param MaxLength token buffer size
* @param k starting arg
* @param Start starting character position in argv[k]
* @param argc, argv the arg list
* @param Delimiters characters that delimit parameters from values
* @return index of the arg containing the value
*
* For example, with Delimiters = ":", the routine parses arg lists of
* the form
* {"param1:value1", "param2:value2", "param3:value3"}.
* It is flexible to allow argument breaks around the delimiter, including
* any of the following syntaxes:
* {"param", "value"},
* {"param:", "value"},
* {"param", ":", "value"},
* {"param", ":", "", "value"}.
*
* The routine can be used as
@code
char TokenBuf[256];
int k = 1;
while(k < argc)
{
kread = CliParseArglist(&Param, &Value, TokenBuf, sizeof(TokenBuf),
k, argv[k], argc, argv, ":");
printf("Read parameter %s = value %s\n", Param, Value);
k = kread + 1;
}
@endcode
*/
int CliParseArglist(const char **Param, const char **Value,
char *TokenBuf, int MaxLength, int k, const char *Start,
int argc, const char *argv[], const char *Delimiters)
{
int TokLen, kread = k;
*Param = *Value = NULL;
TokLen = GetStrToken(TokenBuf, Start, MaxLength, Delimiters);
if(TokLen > MaxLength) /* Token is too long */
*Value = Start;
else
{
*Param = TokenBuf;
/* Check for a non-null character after token (a delimiter)
followed by at least one more non-null character. */
if(Start[TokLen] && Start[TokLen + 1])
*Value = &Start[TokLen + 1];
else /* Otherwise, scan ahead for the value */
for(kread = k + 1; kread < argc; kread++)
if(!argv[kread][0]) /* Null arg */
continue;
else if(!strchr(Delimiters, argv[kread][0]))
{
*Value = &argv[kread][0];
break;
}
else if(argv[kread][1])
{
*Value = &argv[kread][1]; /* Skip first character */
break;
}
}
return kread;
}
/** @brief Strictly read a num value */
int CliGetNum(num *Value, const char *String, const char *Param)
{
double DoubleValue;
int i;
if(!Value)
{
fprintf(stderr, "Null pointer.\n");
return 0;
}
else if(!String)
{
fprintf(stderr, "Expected a number for %s.\n", Param);
return 0;
}
i = ParseDouble(&DoubleValue, String);
if(!i || String[i]) /* No number read, or ends with non-null character */
{
*Value = 0;
fprintf(stderr, "Invalid syntax \"%s\".\n", String);
return 0;
}
else
{
*Value = (num)DoubleValue;
return 1;
}
}
/** @brief Read a matrix from a text file */
int ReadMatrixFromTextFile(image *f, const char *FileName)
{
FILE *File;
num *Dest = NULL;
double Value;
long DestNumEl = 0, DestCapacity = 64;
int c, Line = 1, Col = 0, NumRows = 0, NumCols = 0;
if(!f)
return 0;
*f = NullImage;
if(!(File = fopen(FileName, "rt")))
{
fprintf(stderr, "Error reading \"%s\":\n", FileName);
fprintf(stderr, "Unable to open file.\n");
return 0;
}
/* Allocate an initial destination buffer, it will be resized as needed. */
if(!(Dest = (num *)malloc(sizeof(num)*DestCapacity)))
goto Catch;
while(1)
{
/* Eat whitespace */
do
{
c = getc(File);
if(c == '\n' || c == '\r' || !isspace(c))
break;
else if(ferror(File))
{
fprintf(stderr, "Error reading \"%s\".\n", FileName);
goto Catch;
}
}while(!feof(File));
if(c == '#')
{
/* Found a comment, ignore the rest of the line. */
do
{
c = getc(File);
if(c == '\n' || c == '\r')
break;
else if(ferror(File))
{
fprintf(stderr, "Error reading \"%s\".\n", FileName);
goto Catch;
}
}while(!feof(File));
}
if(c == EOF || c == '\n' || c == '\r')
{
if(Col) /* End of a non-empty line */
{
if(!NumCols)
NumCols = Col;
else if(NumCols != Col)
{
fprintf(stderr,
"Error reading \"%s\" on line %d:\n"
"Rows must have a consistent number of elements.\n",
FileName, Line);
goto Catch;
}
NumRows++;
Col = 0;
}
if(c == EOF)
break;
Line++;
}
else
{
/* There should be a number, try to read it. */
ungetc(c, File);
if(fscanf(File, "%lg", &Value) != 1)
{
fprintf(stderr,
"Error reading \"%s\" on line %d:\nInvalid number.\n",
FileName, Line);
goto Catch; /* Failed to parse number */
}
/* Put Value into Dest */
if(DestNumEl == DestCapacity)
{
/* Increase Dest capacity by 10% */
DestCapacity += DestCapacity/10 + 1;
if(!(Dest = (num *)realloc(Dest, sizeof(num)*DestCapacity)))
{
fprintf(stderr, "Memory allocation failed.\n");
goto Catch;
}
}
Dest[DestNumEl++] = (num)Value;
Col++;
}
}
fclose(File);
f->Data = Dest;
f->Width = NumCols;
f->Height = NumRows;
f->NumChannels = 1;
return 1;
Catch:
fclose(File);
if(Dest)
free(Dest);
return 0;
}
/** @brief Read a matrix from a text or image file */
int ReadMatrixFromFile(image *f, const char *FileName,
int (*RescaleFun)(image *f))
{
char Type[8];
if(!f)
return 0;
/* If the file is not a known image type, attempt to read it as
a text file. */
if(!IdentifyImageType(Type, FileName))
return ReadMatrixFromTextFile(f, FileName);
/* The file appears to be an image type, attempt to read it. */
if(!ReadImageObjGrayscale(f, FileName))
{
fprintf(stderr, "Error reading \"%s\".\n", FileName);
return 0;
}
if(RescaleFun && !RescaleFun(f))
{
FreeImageObj(*f);
*f = NullImage;
return 0;
}
return 1;
}