/* 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; }