/* Copyright (C) 2008-2009 tks
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/* Todo: atoi error handling */
/* help is -h in manual */
#include
#define _ATFILE_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _GNU_SOURCE
#include
/* Need to use libpng > ver 1.0.6 */
#include
#include
#define PNG_SETJMP_NOT_SUPPORTED 1
#define PROGRESS_SHARPS 50 /* total number of progress sharps */
int quiet = 0; /* 1: output is quiet, 0: not quiet */
int dry_run = 0; /* 1:don't output PNGs and conf is output to stdout. */
#define VERBOSE_PRINT(...) \
if (!quiet) { fprintf (stderr, __VA_ARGS__); }
enum DIR_STATE {
NON,
UP,
DOWN,
DEAD
};
typedef struct {
int state; /* NON, UP, DOWN, DEAD. NON means end of the array of dirNameS. */
const char *dirp; /* pointer to directory name. used only if state is DOWN. */
int length; /* length of directory name, used only if state is DOWN. */
} dirNameS ;
void parseOptions (int argc, char *argv[], char **confp,
char **dirp, int *suffixp, const char **cursorp);
void printUsage (int status);
void removeLastSlash (char *string);
const char *rawName (const char *cursor);
char *makeConfPath (char *conf, const char *rawname);
FILE *openConfStream (const char *conf);
void dirIsWritable (const char *dir);
char *getPrefixFromConfToOut (const char *conf, const char *out,
const char *cwd);
int countSlashes (const char *string);
void appartDirName (dirNameS retArray[], int max, const char *string);
void trimDirName (dirNameS Array[]);
void killCommonPart (dirNameS a[], dirNameS b[]);
void reverseDirName (dirNameS Array[], const char *cwd);
void setStateOfDirNameS (dirNameS *dir);
void initializeDirName (dirNameS Array[], int len);
char *writePathFromDirNames (dirNameS a[], dirNameS b[]);
void writePathFromDirName (char **p, dirNameS a[]);
int lengthOfDirName (dirNameS a[]);
int saveConfAndPNGs (const XcursorImages *xcIs, const char *xcurFilePart,
int suffix, FILE *conffp, const char *imagePrefix,
const char *outdir);
void printProgress (int num, int total);
int writePngFileFromXcur (const XcursorDim width, const XcursorDim height,
const XcursorPixel* pixels, const char* pngName);
void parseOptions (int argc, char* argv[], char** confp,
char** dirp, int* suffixp, const char** cursorp)
{
int ret;
extern char *optarg;
extern int optind;
extern int quiet;
extern int dry_run;
const struct option longopts[] =
{
{"version", no_argument, NULL, 'V'},
{"help", no_argument, NULL, 'h'},
{"conf", required_argument, NULL, 'c'},
{"directory", required_argument, NULL, 'd'},
{"initial-suffix", required_argument, NULL, 'i'},
{"quiet", no_argument, NULL, 'q'},
{"dry-run", no_argument, NULL, 'n'},
{NULL, 0, NULL, 0}
};
while (ret = getopt_long (argc, argv, "Vhc:d:i:qn", longopts, NULL))
{
if (ret == -1)
break;
switch (ret)
{
case -1:
break;
case 'V':
printf("xcur2png version %s\n",PACKAGE_VERSION);
exit (0);
case 'h':
printUsage(0);
case 'c':
if (!optarg || *confp != NULL)
printUsage(2);
*confp = optarg;
break;
case 'd':
if (!optarg || *dirp != NULL)
printUsage(2);
*dirp = optarg;
break;
case 'i':
if (!optarg)
printUsage(2);
*suffixp = atoi (optarg);
if (*suffixp >= 999)
{
fprintf (stderr, "Initial suffix must be less than 1000!\n");
exit (2);
}
else if (*suffixp < 0)
{
fprintf (stderr, "Initial suffix must be positive!\n");
exit (2);
}
break;
case 'q':
if (quiet == 1)
printUsage(2);
quiet = 1;
break;
case 'n':
if (dry_run == 1)
printUsage(2);
dry_run = 1;
break;
case '?':
printUsage(2);
break;
default:
break;
}
}
if (optind < argc - 1)
{
fprintf (stderr, "You can specify only one Xcursor at a time\n");
exit (2);
}
if (optind > argc - 1)
{
fprintf (stderr, "Target Xcursor is not specified!\n");
exit (2);
}
*cursorp = argv[optind];
if (dry_run)
{ /* if --dry-run specified, conf is output to stdout and be quiet. */
*confp = "-";
quiet = 1;
}
return;
}
void printUsage (int status)
{ /* print usage and exit with status */
fprintf(stderr,"usage: xcur2png [OPTION] [Xcursor file]\n");
fprintf(stderr,"Take PNG images from Xcursor and generate xcursorgen config-file\n");
fprintf(stderr,"\n");
fprintf(stderr," -V, --version display the version number and exit\n");
fprintf(stderr," -h, --help display this message and exit\n");
fprintf(stderr," -c, --conf [conf] path to config-file which is reusable\n");
fprintf(stderr," by xcursorgen\n");
fprintf(stderr," -d, --directory [dir] directory where PNG images are saved.\n");
fprintf(stderr," -i, --initial-suffix [n] initial suffix which is attached to PNG\n");
fprintf(stderr," -q, --quiet suppress progress message.\n");
fprintf(stderr," -n, --dry-run don't output images and config-file to files.\n");
fprintf(stderr,"\n");
fprintf(stderr,"If [conf] is \'-\', write to standard output.\n");
fprintf(stderr,"If no [conf] is specified, raw-filename of [Xcursor file]\n");
fprintf(stderr,"followed by \".conf\" is used.\n");
fprintf(stderr,"If [dir] is not specified, current directory is used.\n");
fprintf(stderr,"[n] must be positive integer no more than 999.\n");
fprintf(stderr,"Make sure that when suffix go up to 999, then xcur2png stops.\n");
exit (status);
}
const char *rawName (const char *cursor)
{
char *tmpchar = strrchr (cursor, '/');
if (!tmpchar)
{
return cursor;
}
if (tmpchar[1] == '\0')
{
fprintf (stderr, "\"%s\" is not a file.\n", cursor);
exit(-1);
}
return tmpchar + 1;
}
char *makeConfPath (char *conf, const char *rawname)
{
char *ret;
struct stat buf;
removeLastSlash (conf);
if (!conf)
{ /* conf is not specified */
ret = malloc ((strlen (rawname) + 6) * sizeof (char)); /* rawname + ".conf\0" */
sprintf (ret, "%s.conf", rawname);
return ret;
}
if ((stat (conf, &buf) == 0) && S_ISDIR (buf.st_mode))
{ /* conf is exist and directory */
ret = malloc ((strlen (conf) + strlen (rawname) + 7) *sizeof (char));
sprintf (ret, "%s/%s.conf", conf, rawname);
return ret;
}
return strdup (conf);
}
FILE *openConfStream (const char *conf)
{
FILE *ret;
/* open conf stream */
if (strcmp (conf, "-") == 0)
{
ret = stdout;
}
else
{
ret = fopen (conf, "w");
if (!ret)
{
int e = errno ;
fprintf (stderr, "Cannot open \"%s\":%s\n", conf, strerror (e));
exit (1);
}
}
return ret;
}
void dirIsWritable (const char *dir)
{
int e;
struct stat buf;
/* Is dir is directory? */
if (stat (dir, &buf) != 0)
{
e = errno;
fprintf (stderr, "%s:%s\n", dir, strerror(e));
exit (1);
}
else if (!S_ISDIR (buf.st_mode))
{
fprintf (stderr, "%s is not a directory!\n", dir);
exit (1);
}
/* Is output directory writable ? */
if (faccessat (AT_FDCWD, dir, W_OK | X_OK, AT_EACCESS) != 0)
{
e = errno;
fprintf (stderr, "%s:%s\n", dir, strerror (e));
exit (1);
}
return;
}
void removeLastSlash (char *str)
{
if (!str)
return;
int len = strlen (str);
if (len < 2) /* ignore if str is "/". */
return;
if (str[len - 1] == '/')
str[len - 1] = '\0';
return;
}
char *
getPrefixFromConfToOut (const char *conf, const char *out, const char *cwd)
{ /* return path from conf to out. retruned path must be freed later */
char *ret;
int conf_num;
int out_num;
if (conf[0] == '/' || conf[0] == '~' || out[0] == '/' || out[0] == '~')
{ /* abusolute path */
ret = malloc ((strlen (out) + 2) * sizeof (char));
sprintf (ret, "%s/", out);
return ret;
}
else /* relative path */
{
if (strcmp (conf, "-") == 0)
{
if (strcmp (out, ".") == 0)
{
ret = malloc (sizeof (char));
ret[0] = '\0';
return ret;
}
else
{
ret = malloc ((strlen (out) + 2) * sizeof (char));
sprintf (ret, "%s/", out);
return ret;
}
}
else /* conf is not "-" */
{
conf_num = countSlashes (conf);
dirNameS confDirArray[conf_num + 1];
initializeDirName (confDirArray, conf_num + 1);
appartDirName (confDirArray, conf_num, conf);
trimDirName (confDirArray);
out_num = countSlashes (out);
dirNameS outDirArray[out_num + 2];
initializeDirName (outDirArray, out_num + 2);
appartDirName (outDirArray, out_num + 1, out);
trimDirName (outDirArray);
killCommonPart (confDirArray, outDirArray);
reverseDirName (confDirArray, cwd);
ret = writePathFromDirNames (confDirArray, outDirArray);
return ret;
}
}
}
int countSlashes (const char *string)
{ /* count slashes but contiguous slashes is counted as one. */
int i, slash = 0;
for (i = 0; string[i] != '\0'; i++)
{
if (string[i] == '/')
{
++slash;
while (string[i + 1] == '/')
{ ++i;}
}
}
return slash;
}
void appartDirName (dirNameS retArray[], int max, const char *string)
{ /* Appart string with '/', then enter elements in "retArray" up to "max" counts. */
int i = 0, j = -1;
while (j < max)
{
if (string[i] == '/' || string[i] == '\0')
{
if (j > -1)
{
retArray[j].length = (string + i) - retArray[j].dirp;
setStateOfDirNameS (retArray + j);
}
for (; string[i] == '/'; ++i)
{ ;}
if (string[i] == '\0')
{break;}
}
else
{
++j;
retArray[j].dirp = string + i;
for (; string[i] != '/' && string[i] != '\0'; ++i)
{ ;}
}
}
return;
}
void trimDirName (dirNameS Array[])
{
/* if DOWN is followed by UP, the two Elements are DEAD. */
/* Make sure Array.state must be NON terminated. */
int i, j;
int downs_count = 0;
for (i = 0; Array[i].state; ++i)
{
if (Array[i].state == DOWN)
{
++downs_count;
}
else if (downs_count > 0 && Array[i].state == UP)
{
--downs_count;
Array[i].state = DEAD;
for (j = i; ; --j)
{
if (Array[j].state == DOWN)
{
Array[j].state = DEAD;
break;
}
if (j < 0)
{
fprintf (stderr, "Error occur in function \"trimDirName\"!");
exit (1);
}
}
}
}
return;
}
void killCommonPart (dirNameS a[], dirNameS b[])
{
int i, j;
for (i = 0, j = 0; a[i].state != NON && b[i].state != NON; ++i, ++j)
{
while (a[i].state == DEAD)
{
++i;
}
while (b[j].state == DEAD)
{
++j;
}
if (a[i].state == NON || b[j].state == NON)
break;
if ((a[i].state == UP && b[j].state == UP) ||
((a[i].state == DOWN && b[j].state == DOWN) &&
a[i].length == b[j].length &&
strncmp (a[i].dirp, b[j].dirp, a[i].length) == 0))
{
a[i].state = b[j].state = DEAD;
}
}
return;
}
void reverseDirName (dirNameS array[], const char *cwd)
{
/* reverse inputArray. inputArray.state must be NON terminated and trimed
* by trimDirName beforehand. New DOWN directory name is
* gotten from current directory.*/
int i;
int len;
for (len = 0; array[len].state; ++len)
{;}
if (len == 0)
{return;}
dirNameS tmpArray[len + 1];
initializeDirName (tmpArray, len + 1);
int num_cdirs = countSlashes (cwd);
dirNameS currentArray[num_cdirs + 1];
initializeDirName (currentArray, num_cdirs + 1);
appartDirName (currentArray, num_cdirs, cwd);
for (i = 0; i < len; ++i)
{
if (array[i].state == UP)
{
--num_cdirs;
if (num_cdirs >= 0)
{
memcpy (tmpArray + len - i - 1, currentArray + num_cdirs, sizeof (dirNameS));
}
else
{
tmpArray[len - i - 1].state = DEAD;
}
}
else if (array[i].state == DOWN)
{
tmpArray[len - i - 1].state = UP;
}
}
memcpy (array, tmpArray, len * sizeof (dirNameS));
return;
}
void setStateOfDirNameS (dirNameS* dirsp)
{
if (dirsp->length == 1)
{
if (dirsp->dirp[0] == '.')
{
dirsp->state = DEAD;
}
}
else if (dirsp->length == 2)
{
if (strncmp (dirsp->dirp, "..", 2) == 0)
{
dirsp->state = UP;
}
}
else
{
dirsp->state = DOWN;
}
return;
}
void printProgress (int num, int total)
{
static int done = 0; /* number of sharps written before call */
int result; /*number of sharps which should be printed after call */
result = PROGRESS_SHARPS * (num + 1) / total;
for (; done < result; ++done)
{
VERBOSE_PRINT ("#");
}
return;
}
int writePngFileFromXcur (const XcursorDim width, const XcursorDim height,
const XcursorPixel* pixels, const char* pngName)
{
FILE *fp = fopen(pngName, "wb");
if (!fp)
{
fprintf(stderr, "\nCannot write \"%s\".\n", pngName);
return -1;
}
png_voidp user_error_ptr=NULL;
png_error_ptr user_error_fn=NULL, user_warning_fn=NULL;
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn);
if (!png_ptr)
{
fclose (fp);
return -1;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
fclose (fp);
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return -1;
}
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//Write file info.
png_write_info(png_ptr, info_ptr);
//convert BGRA -> RGBA
png_set_bgr(png_ptr);
//Get back non-premuliplied RGB value by alpha fraction.
//We cannot get original RGB value because xcursorgen multiply
//PNG's R by alpha to get Xcursor's R. (Same applies to G and B.)
//This becomes more of a problem if alpha is too small.
//But the error will be reduced enough when you regenerate Xcursor
//from PNGs with xcursorgen.
int i;
XcursorPixel pix[width * height];
for (i=0; i < width * height; i++)
{
unsigned int alpha = pixels[i]>>24;
if (alpha == 0)
{
pix[i] = pixels[i];
continue;
}
unsigned int red = (pixels[i]>>16) & 0xff;
unsigned int green = (pixels[i]>>8) & 0xff;
unsigned int blue = pixels[i] & 0xff;
red = (div (red * 255, alpha).quot) & 0xff;
green = (div (green * 255, alpha).quot) & 0xff;
blue = (div (blue * 255, alpha).quot) & 0xff;
pix[i] = (alpha << 24) + (red << 16) + (green << 8) + blue;
}
png_byte *row_pointers[height];
for (i = 0; i < height ;i++)
{
row_pointers[i] = (png_byte *) (pix + width*i);
}
//Write the image data.
png_write_image(png_ptr, row_pointers);
png_write_end(png_ptr, NULL);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return 1;
}
void initializeDirName (dirNameS Array[], int len)
{
int i;
for (i = 0; i < len; ++i)
{
Array[i].dirp = NULL; //Todo
Array[i].length = 0;
Array[i].state = NON;
}
return;
}
void writePathFromDirName (char **p, dirNameS a[])
{
int i;
for (i = 0; a[i].state; ++i)
{
if (a[i].state == UP)
{
strcpy (*p, "../");
*p += 3;
}
else if (a[i].state == DOWN)
{
strncpy (*p, a[i].dirp, a[i].length);
*p += a[i].length;
**p = '/';
++*p;
}
}
return;
}
char *writePathFromDirNames (dirNameS a[], dirNameS b[])
{ /* return path generated by a[] and b[] */
char *ret = malloc (lengthOfDirName (a) + lengthOfDirName (b) + 1);
char *p = ret;
writePathFromDirName (&p, a);
writePathFromDirName (&p, b);
*p = '\0';
return ret;
}
int lengthOfDirName (dirNameS a[])
{ /* get length of path which is generated by a[](not include '\0' */
int i, len;
for (i = 0, len = 0; a[i].state != NON; ++i)
{
if (a[i].state == UP)
len += 3; /* "../" */
else if (a[i].state == DOWN)
len += a[i].length + 1;
}
return len;
}
int saveConfAndPNGs (const XcursorImages* xcIs, const char* xcurFilePart, int suffix,
FILE* conffp, const char* imagePrefix, const char* outdir)
{
int i;
int ret;
int count = 0;
char pngName[PATH_MAX] = {0};
extern dry_run;
//Write comment on config-file.
fprintf (conffp,"#size\txhot\tyhot\tPath to PNG image\tdelay\n");
/* print messages of progress */
VERBOSE_PRINT ("Converting cursor...\n");
for (i = suffix; count < xcIs->nimage; ++i, ++count)
{
//Read each images' properties.
XcursorUInt version = xcIs->images[count]->version;
XcursorDim size = xcIs->images[count]->size;
XcursorDim width = xcIs->images[count]->width;
XcursorDim height = xcIs->images[count]->height;
XcursorDim xhot = xcIs->images[count]->xhot;
XcursorDim yhot = xcIs->images[count]->yhot;
XcursorUInt delay = xcIs->images[count]->delay;
XcursorPixel *pixels = xcIs->images[count]->pixels;
if (version != 1)
{
fprintf(stderr, "xcur2png can only retrieve Xcursor version 1.\n");
return 0;
}
/* Set png image name to save. */
ret = snprintf(pngName, sizeof(pngName), "%s/%s_%03d.png", outdir, xcurFilePart, i);
if (ret < 0 || ret > sizeof (pngName))
{
fprintf(stderr, "Cannot set filename of output PNG!\n");
return 0;
}
/* Write config-file which can be reused by xcursorgen. */
fprintf (conffp,"%d\t%d\t%d\t%s%s_%03d.png\t%d\n", size, xhot, yhot, imagePrefix, xcurFilePart, i, delay);
//Save png file.
if (dry_run)
{ret = 1;}
else
{
ret = writePngFileFromXcur (width, height, pixels, pngName);
}
if (ret == -1)
{
fprintf (stderr, "Error ocurred in function writePngFileFromXcur.\n");
return 0;
}
if (i == 999)
{
fprintf(stderr,"Sorry, xcur2png cannot count over 999.\n");
return 0;
}
printProgress (i, xcIs->nimage);
}
fprintf (stderr, "\nConversion successfully done!(%d images were output.)\n", count);
return 1;
}
int main (int argc, char *argv[])
{
int ret_val = 0;
char *argconf = NULL; /* config-file gotten from argv */
char *conf; /* path of config-file generated by argconf */
const char *cursor = NULL; /* path to source Xcursor file */
const char *raw_name = NULL; /* raw file name of Xcursor */
char *out = NULL; /* output directory */
char *cwd; /* current directory */
int suffix = 0; /* initial suffix */
FILE *conf_strm = NULL; /* stream to config-file */
char *prefix; /* prefix which is prepended to
PNG image name of config-file */
XcursorImages *xcIs = NULL;
/*
* handle command line options.
*/
parseOptions (argc, argv, &argconf, &out, &suffix, &cursor);
/* set raw_name */
raw_name = rawName (cursor);
/* set conf name */
conf = makeConfPath (argconf, raw_name);
/* If is ensured that conf is not NULL */
/* open stream of conf */
conf_strm = openConfStream (conf);
/* If is ensured that conf_strm is opened and writable. */
/* set output directory path */
if (!out)
{
out = ".";
}
removeLastSlash (out);
/* Is output directory is realy directory and writable? */
dirIsWritable (out);
/* Read Xcursor from file specified in argument. */
xcIs = XcursorFilenameLoadAllImages (cursor);
if (!xcIs)
{
fprintf (stderr, "Can't load Xcursor file \"%s\"!\n", cursor);
exit (1);
}
/* OK, all condition is good ! */
/* get current directory */
cwd = getcwd (NULL, 0);
/* Let's get path from conf to directory where PNG images are written. */
prefix = getPrefixFromConfToOut (conf, out, cwd);
/* then write conf and PNGs */
ret_val = saveConfAndPNGs (xcIs, raw_name, suffix, conf_strm, prefix, out);
/* free memory */
XcursorImagesDestroy(xcIs);
fclose (conf_strm);
free (conf);
free (cwd);
free (prefix);
if (!ret_val)
{
exit (1);
}
return 0;
}