X cursor thumbnailer (fixed)



Hello everyone

The thumbnailer I have sent before would sometimes output images larger
than %s, this is fixed now.

Regards, Krzysztof Kosiński
/* 
 * Copyright © 2008 Krzysztof Kosiński
 *
 * xcursor-thumbnailer 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 2 of the License, or
 * (at your option) any later version.
 *
 * xcursor-thumbnailer 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Krzysztof Kosiński <tweenk o2 pl>
 */

/* This is a Nautilus thumbnailer for X cursors. Because the Xcursor library
   designed for reading cursors is missing some important features like
   selective loading of images, so this program reads the cursor files
   directly. See the "xcursor" manpage for the details of X cursor format.
   This program should have the following gconf command key:
   
   xcursor-thumbnailer %i %o %s
   
   To keep this program simple I refrained from using Gnome VFS.
   */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#include <errno.h>

/* this constant is from Xcursor manpage */
#define XC_TYPE_IMAGE 0xfffd0002
/* Xcursor format description uses this CARD32 to denote unsigned ints */
#define CARD32_SIZE sizeof(unsigned int)

/* X cursor file header */
/* Before this header there is a 4-byte magic "Xcur" */
typedef struct _xc_hdr
{
   unsigned header_size;
   unsigned version;
   unsigned ntoc;
} xc_hdr;

/* After the file header there are ntoc of these structures */
typedef struct _xc_toc_entry
{
   unsigned type;    /* Can denote a comment chunk or an image chunk */
   unsigned subtype; /* This field contains the nominal size for image chunks*/
   unsigned pos;     /* Contains absolute chunk header offset */
} xc_toc_entry;

/* this structure is pointed to by the pos field in xc_toc_entry */
typedef struct _xc_image_hdr
{
   unsigned header_size;
   unsigned type;
   unsigned subtype; /* This field contains the nominal size for image chunks*/
   unsigned version;
   unsigned width;
   unsigned height;
   unsigned xhot;    /* Not used */
   unsigned yhot;    /* Not used */
   unsigned delay;   /* Not used */
} xc_image_hdr;

int main(int argc, char **argv)
{
   /* to keep things simple, abort on invalid arguments */
   if(argc != 4) return -1;
   unsigned requested_size = atoi(argv[3]);
   if(requested_size == 0) return EINVAL; /* skip nonsensical %s value too */
   
   FILE *thumb, *cursor;
   /* Open the cursor file for reading */
   cursor = fopen(argv[1], "r");
   if(!cursor) return EIO;
   
   /* 1. Check X cursor magic */
   {
      char magic[5]; magic[4] = '\0';
      fread(&magic, 1, 4, cursor);
      if(strncmp((char*) &magic, "Xcur", 4))
      { /* this is not an Xcursor file, so abort */
         fclose(cursor);
         return EILSEQ;
      }
   }
   
   /* Read the header */
   xc_hdr info;
   fread(&info, CARD32_SIZE, 3, cursor);
   if(info.header_size != 16) /* unknown header length */
   {
      fclose(cursor);
      return EILSEQ;
   }
   
   /* Now read the TOC entries */
   xc_toc_entry *entries = calloc(info.ntoc, sizeof(xc_toc_entry));
   fread(entries, CARD32_SIZE, info.ntoc*3, cursor);
   
   /* Find the closest match for the size. If no match is found, use
      the largest size present - the thumbnail will be scaled, so for best
      quality the image must be large */
   unsigned i, closest_i = 0, closest = 0, use_i = (unsigned) -1;
   for(i=0; i<info.ntoc; ++i)
   {
      if(entries[i].type != XC_TYPE_IMAGE) continue;
      if(entries[i].subtype == requested_size)
      {
         use_i = i;
         break;
      }
      /* for animated cursors, > will cause the first frame to be retrieved */
      if(entries[i].subtype > closest && entries[i].subtype <= requested_size)
      {
         closest_i = i;
         closest = entries[i].subtype;
      }
   }
   if(use_i == (unsigned) -1) use_i = closest_i;
   
   /* Now we know the position of the image chunk that has the best image.
      Read the image chunk header first. */
   xc_image_hdr img_header;
   fseek(cursor, entries[use_i].pos, 0);
   fread(&img_header, CARD32_SIZE, 9, cursor);
   if(img_header.version != 1)
   {
      fclose(cursor);
      return EILSEQ;
   }
   
   /* Now calculate the size, allocate memory and read the image data */
   unsigned img_size = img_header.width * img_header.height;
   unsigned *image_data = malloc(img_size * CARD32_SIZE);
   if(!image_data) return ENOMEM;
   fread(image_data, CARD32_SIZE, img_size, cursor);
   fclose(cursor); /* not needed anymore */
   
   /* create row pointers for libpng write routine */
   png_byte **rows = malloc(img_header.height * CARD32_SIZE);
   for(i=0; i<img_header.height; ++i)
      rows[i] = (char*) (image_data + (i * img_header.width));
   
   /* Write the PNG thumbnail */
   /* 1. Open the thumbnail file for writing */
   thumb = fopen(argv[2], "w");
   if(!thumb) return EIO;
   
   /* 2. Create libpng structures */
   png_struct *thumb_png; png_info *thumb_info;
   thumb_png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
   if(!thumb_png) return ENOMEM;
   thumb_info = png_create_info_struct(thumb_png);
   if(!thumb_info) return ENOMEM;
   
   /* Set the error jump point. libpng will longjmp here on error. Since this
      program runs with no standard output, simply return an error. */
   if(setjmp(png_jmpbuf(thumb_png))) return EIO;
   
   /* 3. Set PNG information
      Incidetally, Xcursor uses the same pixel format as libpng, so no
      additional conversion is necessary. */
   png_init_io(thumb_png, thumb);
   png_set_IHDR(thumb_png, thumb_info, img_header.width, img_header.height,
      /* bits per channel */ 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
      PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
      
   /* 4. Actually write the image */
   png_write_info(thumb_png, thumb_info);
   png_write_image(thumb_png, rows);
   png_write_end(thumb_png, NULL);
   
   /* 5. Clean up and exit */
   png_destroy_write_struct(&thumb_png, &thumb_info);
   free(rows);
   free(image_data);
   
   return 0;
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]