/************************************************************************/
/* Copyright (C) 2002 Paul Duncan                                       */
/*                                                                      */
/* Permission is hereby granted, free of charge, to any person          */
/* obtaining a copy of this software and associated documentation files */
/* (the "Software"), to deal in the Software without restriction,       */
/* including without limitation the rights to use, copy, modify, merge, */
/* publish, distribute, sublicense, and/or sell copies of the Software, */
/* and to permit persons to whom the Software is furnished to do so,    */
/* subject to the following conditions:                                 */
/*                                                                      */
/* The above copyright notice and this permission notice shall be       */
/* included in all copies of the Software, its documentation and        */
/* marketing & publicity materials, and acknowledgment shall be given   */
/* in the documentation, materials and software packages that this      */
/* Software was used.                                                   */
/*                                                                      */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,      */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF   */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                */
/* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY     */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE    */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.               */
/************************************************************************/

#include <stdio.h>
#define X_DISPLAY_MISSING
#include <Imlib2.h>
#include <ruby.h>

#define VERSION "0.3.0"

/****************************/
/* CLASS AND MODULE GLOBALS */
/****************************/
static VALUE mImlib2,
             cError,
             cBorder,
             mCache,
             mColor,
             cRgbaColor,
             cHsvaColor,
             cHlsaColor,
             cCmyaColor,
             cContext,
             cGradient,
             cImage,
             cFont,
             cPolygon;


/**********************************/
/* Imlib2::FileError EXCEPTIONS   */
/* (exceptions and error strings) */
/**********************************/
static struct { 
    char *exception, 
         *description;
} imlib_errors[] = {
  { "NONE",                         "no error" },
  { "FILE_DOES_NOT_EXIST",          "file does not exist" },
  { "FILE_IS_DIRECTORY",            "file is directory" },
  { "PERMISSION_DENIED_TO_READ",    "permission denied to read" },
  { "NO_LOADER_FOR_FILE_FORMAT",    "no loader for file format" },
  { "PATH_TOO_LONG",                "path too long" },
  { "PATH_COMPONENT_NON_EXISTANT",  "path component nonexistant" },
  { "PATH_COMPONENT_NOT_DIRECTORY", "path component not directory" },
  { "PATH_POINTS_OUTSIDE_ADDRESS_SPACE","path points outside address space"},
  { "TOO_MANY_SYMBOLIC_LINKS",      "too many symbolic links" },
  { "OUT_OF_MEMORY",                "out of memory" },
  { "OUT_OF_FILE_DESCRIPTORS",      "out of file descriptors" },
  { "PERMISSION_DENIED_TO_WRITE",   "permission denied to write" },
  { "OUT_OF_DISK_SPACE",            "out of disk space" },
  { "UNKNOWN",                      "unknown or unspecified error" }
};

typedef struct {
  Imlib_Image im;
} ImStruct;


/********************************/
/* COLOR CLASSES                */
/* (RGBA is Imlib_Color struct) */
/********************************/
typedef struct {
  double hue,
         saturation,
         value;
  int    alpha;
} HsvaColor;

typedef struct {
  double hue,
         lightness,
         saturation;
  int    alpha;
} HlsaColor;

typedef struct {
  int cyan,
      magenta, 
      yellow,
      alpha;
} CmyaColor;


/*********************/
/* UTILITY FUNCTIONS */
/*********************/
/* raise an Imlib2::FileError exception based on an Imlib2 error type */
static void raise_imlib_error(int err) {
  char buf[BUFSIZ];

  if (err < IMLIB_LOAD_ERROR_NONE || err > IMLIB_LOAD_ERROR_UNKNOWN)
    err = IMLIB_LOAD_ERROR_UNKNOWN;

  snprintf(buf, BUFSIZ, "raise Imlib2::Error::%s, '%s'\n",
           imlib_errors[err].exception, imlib_errors[err].description);
  rb_eval_string(buf);
}

/* set the context color -- polymorphic based on color class */
static void set_context_color(VALUE color) {
  if (rb_obj_is_kind_of(color, cRgbaColor) == Qtrue) {
    Imlib_Color *c;
    Data_Get_Struct(color, Imlib_Color, c);
    imlib_context_set_color(c->red, c->green, c->blue, c->alpha);
  } else if (rb_obj_is_kind_of(color, cHsvaColor) == Qtrue) {
    HsvaColor *c;
    Data_Get_Struct(color, HsvaColor, c);
    imlib_context_set_color_hsva(c->hue, c->saturation, c->value, c->alpha);
  } else if (rb_obj_is_kind_of(color, cHlsaColor) == Qtrue) {
    HlsaColor *c;
    Data_Get_Struct(color, HlsaColor, c);
    imlib_context_set_color_hsva(c->hue, c->lightness, c->saturation, c->alpha);
  } else if (rb_obj_is_kind_of(color, cCmyaColor) == Qtrue) {
    CmyaColor *c;
    Data_Get_Struct(color, CmyaColor, c);
    imlib_context_set_color_hsva(c->cyan, c->magenta, c->yellow, c->alpha);
  } else {
    rb_raise(rb_eTypeError, "invalid argument type (not "
                            "Imlib2::Color::RgbaColor, "
                            "Imlib2::Color::HvsaColor, "
                            "Imlib2::Color::HslaColor, or "
                            "Imlib2::Color::CmyaColor)");
  }
}

/******************/
/* BORDER METHODS */
/******************/
/*
 * Returns a new Imlib2::Border.
 *
 * Examples:
 *   left, top, right, bottom = 10, 10, 20, 20
 *   border = Imlib2::Border.new left, top, right, bottom
 *
 *   values = [10, 10, 20, 20]
 *   border = Imlib2::Border.new values
 *
 *   edges = {
 *     'left'   => 10,
 *     'right'  => 20,
 *     'top'    => 10,
 *     'bottom' => 20,
 *   }
 *   border = Imlib2::Border.new edges
 */
VALUE border_new(int argc, VALUE *argv, VALUE klass) {
  Imlib_Border *border;
  VALUE b_o;
  
  border = malloc(sizeof(Imlib_Border));
  memset(border, 0, sizeof(Imlib_Border));

  b_o = Data_Wrap_Struct(klass, 0, free, border);
  rb_obj_call_init(b_o, argc, argv);

  return b_o;
}
  
static VALUE border_init(int argc, VALUE *argv, VALUE self) {
  Imlib_Border *border;
  int num_args;
  
  Data_Get_Struct(self, Imlib_Border, border);

  switch (argc) {
    case 1:
      /* must be either an array or a hash */
      switch (TYPE(argv[0])) {
        case T_HASH:
          border->left = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("left")));
          border->top = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("top")));
          border->right = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("right")));
          border->bottom = NUM2INT(rb_hash_aref(argv[0],rb_str_new2("bottom")));
          break;
        case T_ARRAY:
          border->left = NUM2INT(rb_ary_entry(argv[0], 0));
          border->top = NUM2INT(rb_ary_entry(argv[0], 1));
          border->right = NUM2INT(rb_ary_entry(argv[0], 2));
          border->bottom = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* it's a list of values */
      border->left = NUM2INT(argv[0]);
      border->top = NUM2INT(argv[1]);
      border->right = NUM2INT(argv[2]);
      border->bottom = NUM2INT(argv[3]);
      break;
    default:
      /* no initializing values */
      break;
  }

  return self;
}

static VALUE border_left(VALUE self) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  return INT2FIX(b->left);
}

static VALUE border_set_left(VALUE self, VALUE val) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  b->left = NUM2INT(val);
  return val;
}

static VALUE border_right(VALUE self) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  return INT2FIX(b->right);
}

static VALUE border_set_right(VALUE self, VALUE val) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  b->right = NUM2INT(val);
  return val;
}

static VALUE border_top(VALUE self) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  return INT2FIX(b->top);
}

static VALUE border_set_top(VALUE self, VALUE val) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  b->top = NUM2INT(val);
  return val;
}

static VALUE border_bottom(VALUE self) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  return INT2FIX(b->bottom);
}

static VALUE border_set_bottom(VALUE self, VALUE val) {
  Imlib_Border *b;
  Data_Get_Struct(self, Imlib_Border, b);
  b->bottom = NUM2INT(val);
  return val;
}

/*****************/
/* CACHE METHODS */
/*****************/
static VALUE cache_image(VALUE klass) {
  return INT2FIX(imlib_get_cache_size());
}

static VALUE cache_set_image(VALUE klass, VALUE val) {
  imlib_set_cache_size(NUM2INT(val));
  return Qtrue;
}

static VALUE cache_font(VALUE klass) {
  return INT2FIX(imlib_get_font_cache_size());
}

static VALUE cache_set_font(VALUE klass, VALUE val) {
  imlib_set_font_cache_size(NUM2INT(val));
  return Qtrue;
}

static VALUE cache_flush_font(VALUE klass) {
  imlib_flush_font_cache();
  return cache_font(klass);
}

/**********************/
/* RGBA COLOR METHODS */
/**********************/
/*
 * Returns a new Imlib2::Color::RgbaColor.
 *
 * Examples:
 *   r, g, b, a = 255, 0, 0, 255
 *   border = Imlib2::Color::RgbaColor.new r, g, b, a
 *
 *   values = [255, 0, 0, 255]
 *   border = Imlib2::Color::RgbaColor.new values
 *
 */
VALUE rgba_color_new(int argc, VALUE *argv, VALUE klass) {
  Imlib_Color *color;
  VALUE c_o;
  
  color = malloc(sizeof(Imlib_Color));
  memset(color, 0, sizeof(Imlib_Color));

  c_o = Data_Wrap_Struct(klass, 0, free, color);
  rb_obj_call_init(c_o, argc, argv);

  return c_o;
}
  
static VALUE rgba_color_init(int argc, VALUE *argv, VALUE self) {
  Imlib_Color *color = NULL;
  
  Data_Get_Struct(self, Imlib_Color, color);
  switch (argc) {
    case 1:
      /* must be either an array or a hash */
      switch (TYPE(argv[0])) {
        case T_HASH:
          color->red = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("red")));
          color->green = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("green")));
          color->blue = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("blue")));
          color->alpha = NUM2INT(rb_hash_aref(argv[0],rb_str_new2("alpha")));
          break;
        case T_ARRAY:
          color->red = NUM2INT(rb_ary_entry(argv[0], 0));
          color->green = NUM2INT(rb_ary_entry(argv[0], 1));
          color->blue = NUM2INT(rb_ary_entry(argv[0], 2));
          color->alpha = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid arguments (not array or hash)");
      }
      break;
    case 4:
      /* it's a list of values */
      color->red = NUM2INT(argv[0]);
      color->green = NUM2INT(argv[1]);
      color->blue = NUM2INT(argv[2]);
      color->alpha = NUM2INT(argv[3]);
      break;
    default:
      /* no initializing values */
      break;
  }

  return self;
}

static VALUE rgba_color_red(VALUE self) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  return INT2FIX(b->red);
}

static VALUE rgba_color_set_red(VALUE self, VALUE val) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  b->red = NUM2INT(val);
  return val;
}

static VALUE rgba_color_blue(VALUE self) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  return INT2FIX(b->blue);
}

static VALUE rgba_color_set_blue(VALUE self, VALUE val) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  b->blue = NUM2INT(val);
  return val;
}

static VALUE rgba_color_green(VALUE self) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  return INT2FIX(b->green);
}

static VALUE rgba_color_set_green(VALUE self, VALUE val) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  b->green = NUM2INT(val);
  return val;
}

static VALUE rgba_color_alpha(VALUE self) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  return INT2FIX(b->alpha);
}

static VALUE rgba_color_set_alpha(VALUE self, VALUE val) {
  Imlib_Color *b;
  Data_Get_Struct(self, Imlib_Color, b);
  b->alpha = NUM2INT(val);
  return val;
}


/**********************/
/* HSVA COLOR METHODS */
/**********************/
VALUE hsva_color_new(int argc, VALUE *argv, VALUE klass) {
  HsvaColor *color;
  VALUE c_o;
  
  color = malloc(sizeof(HsvaColor));
  memset(color, 0, sizeof(HsvaColor));

  c_o = Data_Wrap_Struct(klass, 0, free, color);
  rb_obj_call_init(c_o, argc, argv);

  return c_o;
}
  
static VALUE hsva_color_init(int argc, VALUE *argv, VALUE self) {
  HsvaColor *color;
  
  Data_Get_Struct(self, HsvaColor, color);

  switch (argc) {
    case 1:
      /* must be either an array or a hash */
      switch (TYPE(argv[0])) {
        case T_HASH:
          color->hue = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("hue")));
          color->saturation = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("saturation")));
          color->value = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("value")));
          color->alpha = NUM2INT(rb_hash_aref(argv[0],rb_str_new2("alpha")));
          break;
        case T_ARRAY:
          color->hue = NUM2DBL(rb_ary_entry(argv[0], 0));
          color->saturation = NUM2DBL(rb_ary_entry(argv[0], 1));
          color->value = NUM2DBL(rb_ary_entry(argv[0], 2));
          color->alpha = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* it's a list of values */
      color->hue = NUM2DBL(argv[0]);
      color->saturation = NUM2DBL(argv[1]);
      color->value = NUM2DBL(argv[2]);
      color->alpha = NUM2INT(argv[3]);
      break;
    default:
      /* no initializing values */
      break;
  }

  return self;
}

static VALUE hsva_color_hue(VALUE self) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  return rb_float_new(b->hue);
}

static VALUE hsva_color_set_hue(VALUE self, VALUE val) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  b->hue = NUM2DBL(val);
  return val;
}

static VALUE hsva_color_value(VALUE self) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  return rb_float_new(b->value);
}

static VALUE hsva_color_set_value(VALUE self, VALUE val) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  b->value = NUM2DBL(val);
  return val;
}

static VALUE hsva_color_saturation(VALUE self) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  return rb_float_new(b->saturation);
}

static VALUE hsva_color_set_saturation(VALUE self, VALUE val) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  b->saturation = NUM2DBL(val);
  return val;
}

static VALUE hsva_color_alpha(VALUE self) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  return INT2FIX(b->alpha);
}

static VALUE hsva_color_set_alpha(VALUE self, VALUE val) {
  HsvaColor *b;
  Data_Get_Struct(self, HsvaColor, b);
  b->alpha = NUM2INT(val);
  return val;
}


/**********************/
/* HLSA COLOR METHODS */
/**********************/
VALUE hlsa_color_new(int argc, VALUE *argv, VALUE klass) {
  HlsaColor *color;
  VALUE c_o;
  
  color = malloc(sizeof(HlsaColor));
  memset(color, 0, sizeof(HlsaColor));

  c_o = Data_Wrap_Struct(klass, 0, free, color);
  rb_obj_call_init(c_o, argc, argv);

  return c_o;
}
  
static VALUE hlsa_color_init(int argc, VALUE *argv, VALUE self) {
  HlsaColor *color;
  
  Data_Get_Struct(self, HlsaColor, color);

  switch (argc) {
    case 1:
      /* must be either an array or a hash */
      switch (TYPE(argv[0])) {
        case T_HASH:
          color->hue = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("hue")));
          color->lightness = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("lightness")));
          color->saturation = NUM2DBL(rb_hash_aref(argv[0], rb_str_new2("saturation")));
          color->alpha = NUM2INT(rb_hash_aref(argv[0],rb_str_new2("alpha")));
          break;
        case T_ARRAY:
          color->hue = NUM2DBL(rb_ary_entry(argv[0], 0));
          color->lightness = NUM2DBL(rb_ary_entry(argv[0], 1));
          color->saturation = NUM2DBL(rb_ary_entry(argv[0], 2));
          color->alpha = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* it's a list of values */
      color->hue = NUM2DBL(argv[0]);
      color->lightness = NUM2DBL(argv[1]);
      color->saturation = NUM2DBL(argv[2]);
      color->alpha = NUM2INT(argv[3]);
      break;
    default:
      /* no initializing values */
      break;
  }

  return self;
}

static VALUE hlsa_color_hue(VALUE self) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  return rb_float_new(b->hue);
}

static VALUE hlsa_color_set_hue(VALUE self, VALUE val) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  b->hue = NUM2DBL(val);
  return val;
}

static VALUE hlsa_color_saturation(VALUE self) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  return rb_float_new(b->saturation);
}

static VALUE hlsa_color_set_saturation(VALUE self, VALUE val) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  b->saturation = NUM2DBL(val);
  return val;
}

static VALUE hlsa_color_lightness(VALUE self) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  return rb_float_new(b->lightness);
}

static VALUE hlsa_color_set_lightness(VALUE self, VALUE val) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  b->lightness = NUM2DBL(val);
  return val;
}

static VALUE hlsa_color_alpha(VALUE self) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  return INT2FIX(b->alpha);
}

static VALUE hlsa_color_set_alpha(VALUE self, VALUE val) {
  HlsaColor *b;
  Data_Get_Struct(self, HlsaColor, b);
  b->alpha = NUM2INT(val);
  return val;
}


/**********************/
/* CMYA COLOR METHODS */
/**********************/
VALUE cmya_color_new(int argc, VALUE *argv, VALUE klass) {
  CmyaColor *color;
  VALUE c_o;
  
  color = malloc(sizeof(CmyaColor));
  memset(color, 0, sizeof(CmyaColor));

  c_o = Data_Wrap_Struct(klass, 0, free, color);
  rb_obj_call_init(c_o, argc, argv);

  return c_o;
}
  
static VALUE cmya_color_init(int argc, VALUE *argv, VALUE self) {
  CmyaColor *color;
  
  Data_Get_Struct(self, CmyaColor, color);

  switch (argc) {
    case 1:
      /* must be either an array or a hash */
      switch (TYPE(argv[0])) {
        case T_HASH:
          color->cyan = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("cyan")));
          color->magenta = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("magenta")));
          color->yellow = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("yellow")));
          color->alpha = NUM2INT(rb_hash_aref(argv[0],rb_str_new2("alpha")));
          break;
        case T_ARRAY:
          color->cyan = NUM2INT(rb_ary_entry(argv[0], 0));
          color->magenta = NUM2INT(rb_ary_entry(argv[0], 1));
          color->yellow = NUM2INT(rb_ary_entry(argv[0], 2));
          color->alpha = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* it's a list of values */
      color->cyan = NUM2INT(argv[0]);
      color->magenta = NUM2INT(argv[1]);
      color->yellow = NUM2INT(argv[2]);
      color->alpha = NUM2INT(argv[3]);
      break;
    default:
      /* no initializing values */
      break;
  }

  return self;
}

static VALUE cmya_color_cyan(VALUE self) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  return INT2FIX(b->cyan);
}

static VALUE cmya_color_set_cyan(VALUE self, VALUE val) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  b->cyan = NUM2INT(val);
  return val;
}

static VALUE cmya_color_yellow(VALUE self) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  return INT2FIX(b->yellow);
}

static VALUE cmya_color_set_yellow(VALUE self, VALUE val) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  b->yellow = NUM2INT(val);
  return val;
}

static VALUE cmya_color_magenta(VALUE self) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  return INT2FIX(b->magenta);
}

static VALUE cmya_color_set_magenta(VALUE self, VALUE val) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  b->magenta = NUM2INT(val);
  return val;
}

static VALUE cmya_color_alpha(VALUE self) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  return INT2FIX(b->alpha);
}

static VALUE cmya_color_set_alpha(VALUE self, VALUE val) {
  CmyaColor *b;
  Data_Get_Struct(self, CmyaColor, b);
  b->alpha = NUM2INT(val);
  return val;
}


/*****************/
/* IMAGE METHODS */
/*****************/
static void im_struct_free(void *val) {
  ImStruct *im = (ImStruct*) val;
  
  if (im) {
    imlib_context_set_image(im->im);
    imlib_free_image();
    free(im);
  }
}

VALUE image_new(VALUE klass, VALUE w, VALUE h) {
  ImStruct *im = malloc(sizeof(ImStruct));
  VALUE im_o;

  im->im = imlib_create_image(NUM2INT(w), NUM2INT(h));
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  rb_obj_call_init(im_o, 0, NULL);

  return im_o;
}

VALUE image_create_using_data(VALUE klass, VALUE w, VALUE h, VALUE data_o) {
  ImStruct *im;
  DATA32 *data;
  VALUE im_o;

  im = malloc(sizeof(ImStruct));
  Data_Get_Struct(data_o, DATA32, data);
  im->im = imlib_create_image_using_data(NUM2INT(w), NUM2INT(h), data);
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  rb_obj_call_init(im_o, 0, NULL);

  return im_o;
}

VALUE image_create_using_copied_data(VALUE klass, VALUE w, VALUE h, VALUE data_o) {
  ImStruct *im;
  DATA32 *data;
  VALUE im_o;

  im = malloc(sizeof(ImStruct));
  Data_Get_Struct(data_o, DATA32, data);
  im->im = imlib_create_image_using_copied_data(NUM2INT(w), NUM2INT(h), data);
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  rb_obj_call_init(im_o, 0, NULL);

  return im_o;
}

static VALUE image_load(VALUE klass, VALUE filename) {
  ImStruct        *im;
  Imlib_Image      iim;
  Imlib_Load_Error err;
  VALUE            im_o = Qnil;

  iim = imlib_load_image_with_error_return(STR2CSTR(filename), &err);
  if (err == IMLIB_LOAD_ERROR_NONE) {
    im = malloc(sizeof(ImStruct));
    im->im = iim;
    im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);

    if (rb_block_given_p())
      rb_yield(im_o);
  } else {
    /* there was an error loading -- throw an exception if we weren't
     * passed a block */

    if (!rb_block_given_p())
      raise_imlib_error(err);
  }
  
  return im_o;
}

static VALUE image_load_image(VALUE klass, VALUE filename) {
  ImStruct *im = malloc(sizeof(ImStruct));
  VALUE im_o;

  im->im = imlib_load_image(STR2CSTR(filename));
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  
  return im_o;
}

static VALUE image_load_immediately(VALUE klass, VALUE filename) {
  ImStruct *im = malloc(sizeof(ImStruct));
  VALUE im_o;

  im->im = imlib_load_image_immediately(STR2CSTR(filename));
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  
  return im_o;
}

static VALUE image_load_without_cache(VALUE klass, VALUE filename) {
  ImStruct *im = malloc(sizeof(ImStruct));
  VALUE im_o;

  im->im = imlib_load_image_without_cache(STR2CSTR(filename));
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  
  return im_o;
}

static VALUE image_load_immediately_without_cache(VALUE klass, VALUE filename) {
  ImStruct *im = malloc(sizeof(ImStruct));
  VALUE im_o;

  im->im = imlib_load_image_immediately_without_cache(STR2CSTR(filename));
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  
  return im_o;
}

/* returns a hash with "image" -> image, and "error" -> errornum */
static VALUE image_load_with_error_return(VALUE klass, VALUE filename) {
  ImStruct *im = malloc(sizeof(ImStruct));
  Imlib_Load_Error er;
  VALUE hash, im_o;
  
  im->im = imlib_load_image_with_error_return(STR2CSTR(filename), &er);
  im_o = Data_Wrap_Struct(klass, 0, im_struct_free, im);
  
  hash = rb_hash_new();
  rb_hash_aset(hash, rb_str_new2("image"), im_o);
  rb_hash_aset(hash, rb_str_new2("error"), INT2FIX(er));

  return hash;
}

static VALUE image_save(VALUE self, VALUE val) {
  ImStruct *im;
  Imlib_Load_Error er;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_save_image_with_error_return(STR2CSTR(val), &er);

  if (er == IMLIB_LOAD_ERROR_NONE)
    return self;
  if (er < IMLIB_LOAD_ERROR_NONE || er > IMLIB_LOAD_ERROR_UNKNOWN)
    er = IMLIB_LOAD_ERROR_UNKNOWN;
  raise_imlib_error(er);
  
  return Qnil;
}

static VALUE image_save_image(VALUE self, VALUE val) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_save_image(STR2CSTR(val));

  return self;
}

static VALUE image_save_with_error_return(VALUE self, VALUE val) {
  ImStruct *im;
  Imlib_Load_Error er;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_save_image_with_error_return(STR2CSTR(val), &er);

  if (er < IMLIB_LOAD_ERROR_NONE || er > IMLIB_LOAD_ERROR_UNKNOWN)
    er = IMLIB_LOAD_ERROR_UNKNOWN;
  
  return INT2FIX(er);
}

static VALUE image_clone(VALUE self) {
  ImStruct *old_im, *new_im;
  VALUE im_o;

  new_im = malloc(sizeof(ImStruct));
  Data_Get_Struct(self, ImStruct, old_im);
  imlib_context_set_image(old_im->im);
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  return im_o;
}

static VALUE image_initialize(VALUE self) {
  return self;
}

static VALUE image_width(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  return INT2FIX(imlib_image_get_width());
}

static VALUE image_height(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  return INT2FIX(imlib_image_get_height());
}

static VALUE image_filename(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  return rb_str_new2(imlib_image_get_filename());
}

static VALUE image_data(VALUE self) {
  ImStruct *im;
  DATA32   *data;
  int       w, h;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  w = imlib_image_get_width();
  h = imlib_image_get_width();

  return rb_str_new((char*) imlib_image_get_data(), h * w * 4);
}

static VALUE image_data_ro(VALUE self) {
  ImStruct *im;
  DATA32   *data;
  int       w, h;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  w = imlib_image_get_width();
  h = imlib_image_get_width();

  return rb_str_new((char*) imlib_image_get_data_for_reading_only(), h * w * 4);
}

static VALUE image_put_data(VALUE self, VALUE str) {
  ImStruct *im;
  DATA32   *data;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  data = (DATA32*) STR2CSTR(str);
  imlib_image_put_data(data);

  return Qtrue;
}

static VALUE image_has_alpha(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  return imlib_image_has_alpha() ? Qtrue : Qfalse;
}

static VALUE image_set_has_alpha(VALUE self, VALUE val) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_has_alpha(val == Qtrue);

  return val;
}

static VALUE image_changes_on_disk(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_changes_on_disk();

  return Qtrue;
}

static VALUE image_get_border(VALUE self) {
  ImStruct *im;
  Imlib_Border *border;
  VALUE argv[4];
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  border = malloc(sizeof(Imlib_Border));
  imlib_image_get_border(border);
  argv[0] = INT2NUM(border->left);
  argv[1] = INT2NUM(border->top);
  argv[2] = INT2NUM(border->right);
  argv[3] = INT2NUM(border->bottom);
  free(border);

  return border_new(4, argv, cBorder);
}

static VALUE image_set_border(VALUE self, VALUE border) {
  ImStruct *im;
  Imlib_Border *b;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  Data_Get_Struct(border, Imlib_Border, b);
  imlib_image_set_border(b);

  return border;
}

static VALUE image_get_format(VALUE self) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  return rb_str_new2(imlib_image_format());
}

static VALUE image_set_format(VALUE self, VALUE format) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_format(STR2CSTR(format));

  return format;
}

static VALUE image_irrelevant_format(VALUE self, VALUE val) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_irrelevant_format(val != Qfalse);

  return val;
}

static VALUE image_irrelevant_border(VALUE self, VALUE val) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_irrelevant_border(val != Qfalse);

  return val;
}

static VALUE image_irrelevant_alpha(VALUE self, VALUE val) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_set_irrelevant_alpha(val != Qfalse);

  return val;
}

static VALUE image_query_pixel(VALUE self, VALUE x, VALUE y) {
  ImStruct *im;
  Imlib_Color color;
  VALUE argv[4];
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  imlib_image_query_pixel(NUM2INT(x), NUM2INT(y), &color);
  argv[0] = INT2NUM(color.red);
  argv[1] = INT2NUM(color.green);
  argv[2] = INT2NUM(color.blue);
  argv[3] = INT2NUM(color.alpha);

  return rgba_color_new(4, argv, cRgbaColor);
}

static VALUE image_query_pixel_hsva(VALUE self, VALUE x, VALUE y) {
  ImStruct *im;
  float hue, saturation, value;
  int alpha;
  VALUE argv[4];
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  imlib_image_query_pixel_hsva(NUM2INT(x), NUM2INT(y), &hue, &saturation, &value, &alpha);
  argv[0] = rb_float_new(hue);
  argv[1] = rb_float_new(saturation);
  argv[2] = rb_float_new(value);
  argv[3] = INT2NUM(alpha);

  return hsva_color_new(4, argv, cHsvaColor);
}

static VALUE image_query_pixel_hlsa(VALUE self, VALUE x, VALUE y) {
  ImStruct *im;
  float hue, lightness, saturation;
  int alpha;
  VALUE argv[4];
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  imlib_image_query_pixel_hsva(NUM2INT(x), NUM2INT(y), &hue, &lightness, &saturation, &alpha);
  argv[0] = rb_float_new(hue);
  argv[1] = rb_float_new(lightness);
  argv[2] = rb_float_new(saturation);
  argv[3] = INT2NUM(alpha);

  return hlsa_color_new(4, argv, cHlsaColor);
}

static VALUE image_query_pixel_cmya(VALUE self, VALUE x, VALUE y) {
  ImStruct *im;
  CmyaColor color;
  VALUE argv[4];
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  imlib_image_query_pixel_cmya(NUM2INT(x), NUM2INT(y), &color.cyan, &color.magenta, &color.yellow, &color.alpha);
  argv[0] = INT2NUM(color.cyan);
  argv[1] = INT2NUM(color.magenta);
  argv[2] = INT2NUM(color.yellow);
  argv[3] = INT2NUM(color.alpha);

  return cmya_color_new(4, argv, cCmyaColor);
}

static VALUE image_crop(int argc, VALUE *argv, VALUE self) {
  ImStruct *old_im, *new_im;
  VALUE im_o;
  int x = 0, y = 0, w = 0, h = 0;
  
  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1 or 4)");
  }
  
  Data_Get_Struct(self, ImStruct, old_im);
  imlib_context_set_image(old_im->im);
  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_create_cropped_image(x, y, w, h);
  im_o = Data_Wrap_Struct(cImage, 0, free, new_im);
  
  return im_o;
}

static VALUE image_crop_inline(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  Imlib_Image old_im;
  VALUE im_o;
  int x = 0, y = 0, w = 0, h = 0;
  
  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1 or 4)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  old_im = im->im;
  imlib_context_set_image(old_im);
  im->im = imlib_create_cropped_image(x, y, w, h);
  imlib_context_set_image(old_im);
  imlib_free_image();

  return self;
}


static VALUE image_crop_scaled(int argc, VALUE *argv, VALUE self) {
  ImStruct *old_im, *new_im;
  VALUE im_o;
  int x = 0, y = 0, w = 0, h = 0, dw = 0, dh = 0;
  
  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          dw = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dw")));
          dh = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dh")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          dw = NUM2INT(rb_ary_entry(argv[0], 4));
          dh = NUM2INT(rb_ary_entry(argv[0], 5));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 6:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      dw = NUM2INT(argv[4]);
      dh = NUM2INT(argv[5]);
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1 or 6)");
  }
  
  Data_Get_Struct(self, ImStruct, old_im);
  imlib_context_set_image(old_im->im);
  new_im->im = imlib_create_cropped_scaled_image(x, y, w, h, dw, dh);
  im_o = Data_Wrap_Struct(cImage, 0, free, new_im);

  return im_o;
}

static VALUE image_crop_scaled_inline(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  Imlib_Image old_im;
  VALUE im_o;
  int x = 0, y = 0, w = 0, h = 0, dw = 0, dh = 0;
  
  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          dw = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dw")));
          dh = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dh")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          dw = NUM2INT(rb_ary_entry(argv[0], 4));
          dh = NUM2INT(rb_ary_entry(argv[0], 5));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 6:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      dw = NUM2INT(argv[4]);
      dh = NUM2INT(argv[5]);
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1 or 6)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  old_im = im->im;
  imlib_context_set_image(old_im);
  im->im = imlib_create_cropped_scaled_image(x, y, w, h, dw, dh);
  imlib_context_set_image(old_im);
  imlib_free_image();

  return self;
}

static VALUE image_flip_horizontal(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_flip_horizontal();

  return im_o;
}

static VALUE image_flip_horizontal_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_flip_horizontal();

  return self;
}

static VALUE image_flip_vertical(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_flip_vertical();

  return im_o;
}

static VALUE image_flip_vertical_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_flip_vertical();

  return self;
}

static VALUE image_flip_diagonal(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_flip_diagonal();

  return im_o;
}

static VALUE image_flip_diagonal_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_flip_diagonal();

  return self;
}

static VALUE image_orientate(VALUE self, VALUE val) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  imlib_context_set_image(new_im->im);
  imlib_image_orientate(NUM2INT(val));
  return Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);
}

static VALUE image_orientate_inline(VALUE self, VALUE val) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_orientate(NUM2INT(val));

  return self;
}

static VALUE image_blur(VALUE self, VALUE val) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  imlib_context_set_image(new_im->im);
  imlib_image_blur(NUM2INT(val));
  return Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);
}

static VALUE image_blur_inline(VALUE self, VALUE val) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_blur(NUM2INT(val));

  return self;
}

static VALUE image_sharpen(VALUE self, VALUE val) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  imlib_context_set_image(new_im->im);
  imlib_image_sharpen(NUM2INT(val));
  return Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);
}

static VALUE image_sharpen_inline(VALUE self, VALUE val) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_sharpen(NUM2INT(val));

  return self;
}

static VALUE image_tile_horizontal(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_tile_horizontal();

  return im_o;
}

static VALUE image_tile_horizontal_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_tile_horizontal();

  return self;
}

static VALUE image_tile_vertical(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_tile_vertical();

  return im_o;
}

static VALUE image_tile_vertical_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_tile_vertical();

  return self;
}

static VALUE image_tile(VALUE self) {
  ImStruct *im, *new_im;
  VALUE im_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  im_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  imlib_context_set_image(new_im->im);
  imlib_image_tile();

  return im_o;
}

static VALUE image_tile_inline(VALUE self) {
  ImStruct *im;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_tile();

  return self;
}

static VALUE image_clear(VALUE self) {
  ImStruct *im;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_clear();

  return self;
}

static VALUE image_clear_color(VALUE self, VALUE rgba_color) {
  ImStruct  *im, *new_im;
  Imlib_Color *color;
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  imlib_context_set_image(new_im->im);

  Data_Get_Struct(rgba_color, Imlib_Color, color);
  imlib_image_clear_color(color->red, color->blue, color->green, color->alpha);

  return Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);
}

static VALUE image_clear_color_inline(VALUE self, VALUE rgba_color) {
  ImStruct  *im;
  Imlib_Color *color;
  
  Data_Get_Struct(self, ImStruct, im);
  Data_Get_Struct(rgba_color, Imlib_Color, color);
  imlib_context_set_image(im->im);
  imlib_image_clear_color(color->red, color->blue, color->green, color->alpha);

  return self;
}

static VALUE image_draw_pixel(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int x = 0, y = 0;

  switch (argc) {
    case 1:
      /* one argument is an array or hash of points, with color
       * defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is either two fixnum points (with color
       * defaulting to Qnil), or an array or hash of points and a color
       * */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          color = argv[1];
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          color = argv[1];
          break;
        case T_FIXNUM:
          x = NUM2INT(argv[0]);
          y = NUM2INT(argv[1]);
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is two fixnum points and a color value */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[0]);
      color = argv[2];
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1, 2, or 3)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  (void) imlib_image_draw_pixel(x, y, 0);
  
  return self;
}

static VALUE image_draw_line(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int i = 0, x[2] = {0, 0}, y[2] = {0, 0};

  switch (argc) {
    case 2:
      /* two arguments is an array or hash of points, with color
       * defaulting to Qnil (ie, the context color) */
      for (i = 0; i < 2; i++) {
        switch (TYPE(argv[i])) {
          case T_HASH:
            x[i] = NUM2INT(rb_hash_aref(argv[i], rb_str_new2("x")));
            y[i] = NUM2INT(rb_hash_aref(argv[i], rb_str_new2("y")));
            break;
          case T_ARRAY:
            x[i] = NUM2INT(rb_ary_entry(argv[i], 0));
            y[i] = NUM2INT(rb_ary_entry(argv[i], 1));
            break;
          default:
            rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
        }
      }
      break;
    case 3:
      /* three arguments is two arrays or hashes of points and a color
       * */
      for (i = 0; i < 2; i++) {
        switch (TYPE(argv[i])) {
          case T_HASH:
            x[i] = NUM2INT(rb_hash_aref(argv[i], rb_str_new2("x")));
            y[i] = NUM2INT(rb_hash_aref(argv[i], rb_str_new2("y")));
            break;
          case T_ARRAY:
            x[i] = NUM2INT(rb_ary_entry(argv[i], 0));
            y[i] = NUM2INT(rb_ary_entry(argv[i], 1));
            break;
          default:
            rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
        }
      }
      color = argv[2];
      break;
    case 5:
      /* 5 arguments is 4 fixnum points and a color value */
      x[0] = NUM2INT(argv[0]);
      y[0] = NUM2INT(argv[1]);
      x[1] = NUM2INT(argv[2]);
      y[1] = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 2, 3, or 5)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  (void) imlib_image_draw_line(x[0], y[0], x[1], y[1], 0);
  
  return self;
}


static VALUE image_draw_rect(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int x, y, w, h;

  switch (argc) {
    case 1:
      /* 1 argument is an array or hash of x, y, w, h with color
       * defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of x, y, w, h with a color,
       * or an array or hash of x, y, and an array or hash of w, h (with
       * color defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of x, y and a color */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      color = argv[2];
      break;
    case 4:
      /* 4 arguments is x, y, w, y (color to Qnil) */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    case 5:
      /* 4 arguments is x, y, w, y, color */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError,"invalid argument count (not 1, 2, 3, 4, or 5)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  imlib_image_draw_rectangle(x, y, w, h);
  
  return self;
}


static VALUE image_fill_rect(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int x, y, w, h;

  switch (argc) {
    case 1:
      /* 1 argument is an array or hash of x, y, w, h with color
       * defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of x, y, w, h with a color,
       * or an array or hash of x, y, and an array or hash of w, h (with
       * color defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of x, y and a color */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      color = argv[2];
      break;
    case 4:
      /* 4 arguments is x, y, w, y (color to Qnil) */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    case 5:
      /* 4 arguments is x, y, w, y, color */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 1, 2, 3, 4, or 5)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  imlib_image_fill_rectangle(x, y, w, h);
  
  return self;
}

static VALUE image_copy_alpha(int argc, VALUE *argv, VALUE self) {
  ImStruct *src_im, *im;
  VALUE src;
  int x, y;

  src = argv[0];
  switch (argc) {
    case 2:
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      x = NUM2INT(argv[1]);
      y = NUM2INT(argv[2]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
  }

  Data_Get_Struct(src, ImStruct, src_im);
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_copy_alpha_to_image(src_im->im, x, y);

  return self;
}

static VALUE image_copy_alpha_rect(int argc, VALUE *argv, VALUE self) {
  ImStruct *src_im, *im;
  VALUE src;
  int x, y, w, h, dx, dy;

  src = argv[0];
  switch (argc) {
    case 2:
      /* 2 arguments is a source image and an array or hash of
       * x, y, w, h, dx, dy */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          dx = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          w = NUM2INT(rb_ary_entry(argv[1], 2));
          h = NUM2INT(rb_ary_entry(argv[1], 3));
          dx = NUM2INT(rb_ary_entry(argv[1], 4));
          dy = NUM2INT(rb_ary_entry(argv[1], 5));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is a source image, an array or hash of x,y,w,h,
       * and an array or hash of dx, dy */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          w = NUM2INT(rb_ary_entry(argv[1], 2));
          h = NUM2INT(rb_ary_entry(argv[1], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[2])) {
        case T_HASH:
          dx = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          dx = NUM2INT(rb_ary_entry(argv[1], 0));
          dy = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* four arguments is a source image, an array or hash of [x, y],
       * an array or hash of [w, h], and an array or hash of [dx, dy],
       * or a source image, an array or hash of [x, y, w, h], dx, dy */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));

          switch (TYPE(argv[2])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));

              switch (TYPE(argv[3])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[3], 0));
                  dy = NUM2INT(rb_ary_entry(argv[3], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type " 
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[2], 0));
              h = NUM2INT(rb_ary_entry(argv[2], 1));

              switch (TYPE(argv[3])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[3], 0));
                  dy = NUM2INT(rb_ary_entry(argv[3], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type " 
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              dx = NUM2INT(argv[2]);
              dy = NUM2INT(argv[3]);
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));

          switch (TYPE(argv[2])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));

              switch (TYPE(argv[3])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[3], 0));
                  dy = NUM2INT(rb_ary_entry(argv[3], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type " 
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[2], 0));
              h = NUM2INT(rb_ary_entry(argv[2], 1));

              switch (TYPE(argv[3])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[3], 0));
                  dy = NUM2INT(rb_ary_entry(argv[3], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type " 
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              dx = NUM2INT(argv[2]);
              dy = NUM2INT(argv[3]);
          }
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 5:
      /* five arguments is a source image, an array or hash of [x, y],
       * an array or hash of [w, h], dx, dy */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[2])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[2], 0));
          h = NUM2INT(rb_ary_entry(argv[2], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      dx = NUM2INT(argv[3]);
      dy = NUM2INT(argv[4]);
    case 6:
      /* six arguments is a source image, x, y, w, h, and an array or
       * hash of [dx, dy], or source image, an array or hash of [x, y],
       * an array or hash of [w, h], dx, dy */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));

          switch (TYPE(argv[2])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[2], 0));
              h = NUM2INT(rb_ary_entry(argv[2], 1));
              break;
            default:
              rb_raise(rb_eTypeError, "invalid argument type "
                                      "(not array or hash)");
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));

          switch (TYPE(argv[2])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[2], 0));
              h = NUM2INT(rb_ary_entry(argv[2], 1));
              break;
            default:
              rb_raise(rb_eTypeError, "invalid argument type "
                                      "(not array or hash)");
          }
          break;
        default:
          x = NUM2INT(argv[1]);
          y = NUM2INT(argv[2]);
          w = NUM2INT(argv[3]);
          h = NUM2INT(argv[4]);
      }
      break;
    case 7:
      /* seven arguments is a source image, x, y, w, y, dx, dy */
      x = NUM2INT(argv[1]);
      y = NUM2INT(argv[2]);
      w = NUM2INT(argv[3]);
      h = NUM2INT(argv[4]);
      dx = NUM2INT(argv[5]);
      dy = NUM2INT(argv[6]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count "
                              "(not 2, 3, 4, 5, 6, or 7)");
  }
  
  Data_Get_Struct(src, ImStruct, src_im);
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_copy_alpha_rectangle_to_image(src_im->im, x, y, w, h, dx, dy);
  
  return self;
}

static VALUE image_scroll_rect(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  int x, y, w, h, dx, dy;

  switch (argc) {
    case 1:
      /* one argument is an array or hash of [x, y, w, h, dx, dy] */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          dx = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          dx = NUM2INT(rb_ary_entry(argv[0], 4));
          dy = NUM2INT(rb_ary_entry(argv[0], 5));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of [x, y, w, h] and an array
       * or hash of [dx, dy] */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          dx = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          dx = NUM2INT(rb_ary_entry(argv[1], 0));
          dy = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of [x, y], an array or hash
       * of [w, h] and an array or hash of [dx, dy], or an array or hash
       * of [x, y, w, h], dx, dy */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));

          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              dx = NUM2INT(argv[1]);
              dy = NUM2INT(argv[2]);
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));

          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              dx = NUM2INT(argv[1]);
              dy = NUM2INT(argv[2]);
          }
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* four arguments is an array or hash of [x, y], an array or hash
       * of [w, h], dx, dy */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      dx = NUM2INT(argv[2]);
      dy = NUM2INT(argv[3]);
      break;
    case 5:
      /* five arguments is x, y, w, h, and an array or hash of
       * [dx, dy] */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      switch (TYPE(argv[4])) {
        case T_HASH:
          dx = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          dx = NUM2INT(rb_ary_entry(argv[4], 0));
          dy = NUM2INT(rb_ary_entry(argv[4], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 6:
      /* six arguments is x, y, w, h, dx, dy */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      dx = NUM2INT(argv[4]);
      dy = NUM2INT(argv[5]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count "
                              "(not 2, 3, 4, 5, 6, or 7)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_scroll_rect(x, y, w, h, dx, dy);

  return self;
}

static VALUE image_copy_rect(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  int x, y, w, h, dx, dy;

  switch (argc) {
    case 1:
      /* one argument is an array or hash of [x, y, w, h, dx, dy] */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          dx = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          dx = NUM2INT(rb_ary_entry(argv[0], 4));
          dy = NUM2INT(rb_ary_entry(argv[0], 5));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of [x, y, w, h] and an array
       * or hash of [dx, dy] */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          dx = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          dx = NUM2INT(rb_ary_entry(argv[1], 0));
          dy = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of [x, y], an array or hash
       * of [w, h] and an array or hash of [dx, dy], or an array or hash
       * of [x, y, w, h], dx, dy */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));

          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              dx = NUM2INT(argv[1]);
              dy = NUM2INT(argv[2]);
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));

          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));

              switch (TYPE(argv[2])) {
                case T_HASH:
                  dx = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dx")));
                  dy = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("dy")));
                  break;
                case T_ARRAY:
                  dx = NUM2INT(rb_ary_entry(argv[2], 0));
                  dy = NUM2INT(rb_ary_entry(argv[2], 1));
                  break;
                default:
                  rb_raise(rb_eTypeError, "invalid argument type "
                                          "(not array or hash)");
              }
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              dx = NUM2INT(argv[1]);
              dy = NUM2INT(argv[2]);
          }
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 4:
      /* four arguments is an array or hash of [x, y], an array or hash
       * of [w, h], dx, dy */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      dx = NUM2INT(argv[2]);
      dy = NUM2INT(argv[3]);
      break;
    case 5:
      /* five arguments is x, y, w, h, and an array or hash of
       * [dx, dy] */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      switch (TYPE(argv[4])) {
        case T_HASH:
          dx = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("dx")));
          dy = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("dy")));
          break;
        case T_ARRAY:
          dx = NUM2INT(rb_ary_entry(argv[4], 0));
          dy = NUM2INT(rb_ary_entry(argv[4], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument type (not array or hash)");
      }
      break;
    case 6:
      /* six arguments is x, y, w, h, dx, dy */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      dx = NUM2INT(argv[4]);
      dy = NUM2INT(argv[5]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count "
                              "(not 2, 3, 4, 5, 6, or 7)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  imlib_image_copy_rect(x, y, w, h, dx, dy);

  return self;
}

static VALUE image_draw_ellipse(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int x, y, w, h;

  switch (argc) {
    case 1:
      /* 1 argument is an array or hash of x, y, w, h with color
       * defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of x, y, w, h with a color,
       * or an array or hash of x, y, and an array or hash of w, h (with
       * color defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of x, y and a color */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      color = argv[2];
      break;
    case 4:
      /* 4 arguments is x, y, w, y (color to Qnil) */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    case 5:
      /* 4 arguments is x, y, w, y, color */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 1, 2, 3, 4, or 5)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  imlib_image_draw_ellipse(x, y, w, h);
  
  return self;
}

static VALUE image_fill_ellipse(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  VALUE color = Qnil;
  int x, y, w, h;

  switch (argc) {
    case 1:
      /* 1 argument is an array or hash of x, y, w, h with color
       * defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          w = NUM2INT(rb_ary_entry(argv[0], 2));
          h = NUM2INT(rb_ary_entry(argv[0], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 2:
      /* two arguments is an array or hash of x, y, w, h with a color,
       * or an array or hash of x, y, and an array or hash of w, h (with
       * color defaulting to Qnil (ie, the context color) */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("w")));
              y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("h")));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          switch (TYPE(argv[1])) {
            case T_HASH:
              w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
              h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
              break;
            case T_ARRAY:
              w = NUM2INT(rb_ary_entry(argv[1], 0));
              h = NUM2INT(rb_ary_entry(argv[1], 1));
              break;
            default:
              w = NUM2INT(rb_ary_entry(argv[0], 2));
              h = NUM2INT(rb_ary_entry(argv[0], 3));
              /* we could do a type check here, but if it's invalid
               * it'll get caught in the set_context_color() call */
              color = argv[1];
          }
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      break;
    case 3:
      /* three arguments is an array or hash of x, y and a color */
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      switch (TYPE(argv[1])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[1], 0));
          h = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      color = argv[2];
      break;
    case 4:
      /* 4 arguments is x, y, w, y (color to Qnil) */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      break;
    case 5:
      /* 4 arguments is x, y, w, y, color */
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      w = NUM2INT(argv[2]);
      h = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 1, 2, 3, 4, or 5)");
  }
  
  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  if (color != Qnil)
    set_context_color(color);
  imlib_image_fill_ellipse(x, y, w, h);
  
  return self;
}

static VALUE image_blend_image_inline(int argc, VALUE *argv, VALUE self) {
  ImStruct *im, *src_im;
  int i, s[4], d[4];
  char merge_alpha = 1;
  
  switch (argc) {
    case 4:
      /* four arguments is source image, an array or hash of
       * [sx, sy, sw, sh], an array or hash of [sx, sy, sw, sh], and
       * merge_alpha */
      merge_alpha = (argv[3] == Qtrue) ? 1 : 0;
    case 3:
      /* three arguments is source image, an array or hash of
       * [sx, sy, sw, sh], and an array or hash of [sx, sy, sw, sh], 
       * with merge_alpha defaulting to true */
      switch (TYPE(argv[1])) {
        case T_HASH:
          s[0] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          s[1] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          s[2] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          s[3] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          for (i = 0; i < 4; i++)
            s[i] = NUM2INT(rb_ary_entry(argv[1], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      switch (TYPE(argv[2])) {
        case T_HASH:
          d[0] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("x")));
          d[1] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("y")));
          d[2] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
          d[3] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
          break;
        case T_ARRAY:
          for (i = 0; i < 4; i++)
            d[i] = NUM2INT(rb_ary_entry(argv[2], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      
      break;
    case 6:
      /* six arguments is source image, an array or hash of [sx, sy], an
       * array or hash of [sw, sh], an array or hash of [dx, dy], an
       * array or hash of [dw, dh], and merge_alpha */
      merge_alpha = (argv[5] == Qtrue) ? 1 : 0;
    case 5:
      /* five arguments is source image, an array or hash of [sx, sy], an
       * array or hash of [sw, sh], an array or hash of [dx, dy], and an
       * array or hash of [dw, dh], with merge_alpha defaulting to true */

      switch (TYPE(argv[1])) {
        case T_HASH:
          s[0] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          s[1] = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          break;
        case T_ARRAY:
          for (i = 0; i < 2; i++)
            s[i] = NUM2INT(rb_ary_entry(argv[1], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      switch (TYPE(argv[2])) {
        case T_HASH:
          s[2] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("s")));
          s[3] = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
          break;
        case T_ARRAY:
          for (i = 0; i < 2; i++)
            s[i + 2] = NUM2INT(rb_ary_entry(argv[2], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      switch (TYPE(argv[3])) {
        case T_HASH:
          d[0] = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("x")));
          d[1] = NUM2INT(rb_hash_aref(argv[3], rb_str_new2("y")));
          break;
        case T_ARRAY:
          for (i = 0; i < 2; i++)
            d[i] = NUM2INT(rb_ary_entry(argv[3], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      switch (TYPE(argv[4])) {
        case T_HASH:
          d[2] = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("s")));
          d[3] = NUM2INT(rb_hash_aref(argv[4], rb_str_new2("h")));
          break;
        case T_ARRAY:
          for (i = 0; i < 2; i++)
            d[i + 2] = NUM2INT(rb_ary_entry(argv[4], i));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      break;
    case 10:
      /* ten arguments is source image, sx, sy, sw, sh, dx, dy, dw, dh,
       * and merge_alpha */
      merge_alpha = (argv[9] == Qtrue) ? 1 : 0;
    case 9:
      /* ten arguments is source image, sx, sy, sw, sh, dx, dy, dw, and
       * dh, with merge_alpha defaulting to true */

      for (i = 0; i < 4; i++)
        s[i] = NUM2INT(argv[i +  1]);
      for (i = 0; i < 4; i++)
        d[i] = NUM2INT(argv[i +  5]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count "
                              "(not 3, 4, 5, 6, 9, or 10)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  Data_Get_Struct(argv[0], ImStruct, src_im);
  imlib_blend_image_onto_image(src_im->im, merge_alpha,
                               s[0], s[1], s[2], s[3], 
                               d[0], d[1], d[2], d[3]);
  
  return self;
}

static VALUE image_blend_image(int argc, VALUE *argv, VALUE self) {
  ImStruct *im, *new_im;
  VALUE i_o;

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  new_im = malloc(sizeof(ImStruct));
  new_im->im = imlib_clone_image();
  i_o = Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);

  return image_blend_image_inline(argc, argv, i_o);
}

static VALUE image_rotate(VALUE self, VALUE angle) {
  ImStruct *new_im, *im;
  double a;

  new_im = malloc(sizeof(ImStruct));

  Data_Get_Struct(self, ImStruct, im);
  a = rb_float_new(angle);
  imlib_context_set_image(im->im);
  
  new_im->im = imlib_create_rotated_image(a);
  
  return Data_Wrap_Struct(cImage, 0, im_struct_free, new_im);
}

static VALUE image_rotate_inline(VALUE self, VALUE angle) {
  ImStruct *im;
  Imlib_Image new_im;
  double a;

  Data_Get_Struct(self, ImStruct, im);
  a = rb_float_new(angle);
  imlib_context_set_image(im->im);
  
  new_im = imlib_create_rotated_image(a);
  
  imlib_context_set_image(im->im);
  imlib_free_image();

  im->im = new_im;

  return self;
}

static VALUE image_draw_text(int argc, VALUE *argv, VALUE self) {
  ImStruct   *im;
  Imlib_Font *font;
  VALUE text, ary, color = Qnil;
  int x, y, i, r[] = { 0, 0, 0, 0 };

  switch (argc) {
    case 3:
      /* three arguments is a font, a string, and an array or hash of
       * x, y, with color defaulting to Qnil (the context color) */
      switch (TYPE(argv[2])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[2], 0));
          y = NUM2INT(rb_ary_entry(argv[2], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
      }
      break;
    case 4:
      /* four arguments is a font, a string, x, y, with color defaulting
       * to Qnil, OR a font, a string, an array or hash of [x, y] and a
       * color */
      switch (TYPE(argv[2])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("y")));
          color = argv[3];
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[2], 0));
          y = NUM2INT(rb_ary_entry(argv[2], 1));
          color = argv[3];
          break;
        default:
          x = NUM2INT(argv[2]);
          y = NUM2INT(argv[3]);
      }
      break;
    case 5:
      /* five arguments is a font, a string, x, y, a color */
      x = NUM2INT(argv[2]);
      y = NUM2INT(argv[3]);
      color = argv[4];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 3, 4, or 5)");
  }

  Data_Get_Struct(argv[0], Imlib_Font, font);
  Data_Get_Struct(self, ImStruct, im);
  text = argv[1];

  imlib_context_set_font(*font);
  imlib_context_set_image(im->im);

  if (color != Qnil) 
    set_context_color(color);

  imlib_text_draw_with_return_metrics(x, y, STR2CSTR(text), 
                                      &r[0], &r[1], &r[2], &r[3]);

  ary = rb_ary_new();
  for (i = 0; i < 4; i++)
    rb_ary_push(ary, INT2FIX(r[i]));
  
  return ary;
}

static VALUE image_fill_gradient(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  Imlib_Color_Range *grad;
  int x, y, w, h;
  double angle;
  
  switch (argc) {
    case 3:
      /* three arguments is a gradient, an array or hash of [x,y,w,h],
       * and an angle */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          w = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("h")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          w = NUM2INT(rb_ary_entry(argv[1], 2));
          h = NUM2INT(rb_ary_entry(argv[1], 3));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      angle = NUM2DBL(argv[2]);
      break;
    case 4:
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }

      switch (TYPE(argv[2])) {
        case T_HASH:
          w = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("w")));
          h = NUM2INT(rb_hash_aref(argv[2], rb_str_new2("h")));
          break;
        case T_ARRAY:
          w = NUM2INT(rb_ary_entry(argv[2], 0));
          h = NUM2INT(rb_ary_entry(argv[2], 1));
          break;
        default:
          rb_raise(rb_eTypeError,"invalid argument type (not array or hash)");
      }
      angle = NUM2DBL(argv[3]);
      break;
    case 6:
      x = NUM2INT(argv[1]);
      y = NUM2INT(argv[2]);
      w = NUM2INT(argv[3]);
      h = NUM2INT(argv[4]);
      angle = NUM2DBL(argv[5]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 3, 4, or 6)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);
  Data_Get_Struct(argv[0], Imlib_Color_Range, grad);
  imlib_context_set_color_range(*grad);

  imlib_image_fill_color_range_rectangle(x, y, w, h, angle);

  return self;
}

static VALUE image_draw_poly(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  ImlibPolygon *poly;
  VALUE color = Qnil;
  unsigned char closed = Qtrue;

  switch (argc) {
    case 1:
      /* one argument is poly.. closed is default (Qtrue) */
      break;
    case 2:
      /* two arguments is poly and closed, or poly and color */
      if ((rb_obj_is_kind_of(argv[1], cRgbaColor) == Qtrue) ||
          (rb_obj_is_kind_of(argv[1], cHsvaColor) == Qtrue) ||
          (rb_obj_is_kind_of(argv[1], cHlsaColor) == Qtrue) ||
          (rb_obj_is_kind_of(argv[1], cCmyaColor) == Qtrue)) {
        color = argv[1];
      } else /* FIXME: do type check here */ {
        closed = (argv[1] == Qtrue) ? 1 : 0;
      }
      break;
    case 3:
      /* two arguments is poly, closed, and color */
      closed = (argv[1] == Qtrue) ? 1 : 0;
      color = argv[2];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 3, 4, or 6)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  if (color != Qnil)
    set_context_color(color);
  
  Data_Get_Struct(argv[0], ImlibPolygon, poly);
  imlib_image_draw_polygon(*poly, closed);

  return self;
}

static VALUE image_fill_poly(int argc, VALUE *argv, VALUE self) {
  ImStruct *im;
  ImlibPolygon *poly;
  VALUE color = Qnil;

  switch (argc) {
    case 1:
      /* one argument is poly.. closed is default (Qtrue) */
      break;
    case 2:
      color = argv[1];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 3, 4, or 6)");
  }

  Data_Get_Struct(self, ImStruct, im);
  imlib_context_set_image(im->im);

  if (color != Qnil)
    set_context_color(color);
  
  Data_Get_Struct(argv[0], ImlibPolygon, poly);
  imlib_image_fill_polygon(*poly);

  return self;
}

/******************/
/* FONT FUNCTIONS */
/******************/
static void font_free(void *val) {
  Imlib_Font *font = (Imlib_Font*) val;
  imlib_context_set_font(*font);
  imlib_free_font();
  free(font);
}

VALUE font_new(VALUE klass, VALUE font_name) {
  Imlib_Font *font;
  VALUE f_o;
  
  font = malloc(sizeof(Imlib_Font*));
  *font = imlib_load_font(STR2CSTR(font_name));

  f_o = Data_Wrap_Struct(klass, 0, font_free, font);
  rb_obj_call_init(f_o, 0, NULL);

  return f_o;
}

static VALUE font_init(VALUE self) {
}

static VALUE font_text_size(VALUE self, VALUE text) {
  Imlib_Font *font;
  VALUE ary;
  int   sw, sh;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);
  imlib_get_text_size(STR2CSTR(text), &sw, &sh);
  
  ary = rb_ary_new();
  rb_ary_push(ary, INT2FIX(sw));
  rb_ary_push(ary, INT2FIX(sh));
  
  return ary;
}
  
static VALUE font_text_advance(VALUE self, VALUE text) {
  Imlib_Font *font;
  VALUE ary;
  int   sw, sh;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);
  imlib_get_text_advance(STR2CSTR(text), &sw, &sh);
  
  ary = rb_ary_new();
  rb_ary_push(ary, INT2FIX(sw));
  rb_ary_push(ary, INT2FIX(sh));
  
  return ary;
}
  
static VALUE font_text_inset(VALUE self, VALUE text) {
  Imlib_Font *font;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);
  
  return INT2FIX(imlib_get_text_inset(STR2CSTR(text)));
}
  
static VALUE font_text_index(int argc, VALUE *argv, VALUE self) {
  Imlib_Font *font;
  VALUE text, ary;
  int x, y, i, r[] = { 0, 0, 0, 0 };

  text = argv[0];
  switch (argc) {
    case 2:
      /* two arguments is a string, and an array or hash of x, y */
      switch (TYPE(argv[1])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[1], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[1], 0));
          y = NUM2INT(rb_ary_entry(argv[1], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
      }
      break;
    case 3:
      /* three arguments is a string, x, y */
      x = NUM2INT(argv[1]);
      y = NUM2INT(argv[2]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
  }

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);
  imlib_text_get_index_and_location(STR2CSTR(text), x, y,
                                    &r[0], &r[1], &r[2], &r[3]);
  ary = rb_ary_new();
  for (i = 0; i < 4; i++)
    rb_ary_push(ary, INT2FIX(r[i]));
  
  return ary;
}
  
static VALUE font_text_location(VALUE self, VALUE text, VALUE index) {
  Imlib_Font *font;
  VALUE ary;
  int i, r[] = { 0, 0, 0, 0 };

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);
  imlib_text_get_location_at_index(STR2CSTR(text), NUM2INT(index), 
                                   &r[0], &r[1], &r[2], &r[3]);

  ary = rb_ary_new();
  for (i = 0; i < 4; i++)
    rb_ary_push(ary, INT2FIX(r[i]));
  
  return ary;
}
  
static VALUE font_ascent(VALUE self) {
  Imlib_Font *font;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);

  return INT2FIX(imlib_get_font_ascent());
}

static VALUE font_descent(VALUE self) {
  Imlib_Font *font;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);

  return INT2FIX(imlib_get_font_descent());
}
  
static VALUE font_maximum_ascent(VALUE self) {
  Imlib_Font *font;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);

  return INT2FIX(imlib_get_maximum_font_ascent());
}

static VALUE font_maximum_descent(VALUE self) {
  Imlib_Font *font;

  Data_Get_Struct(self, Imlib_Font, font);
  imlib_context_set_font(*font);

  return INT2FIX(imlib_get_maximum_font_descent());
}

static VALUE font_list_fonts(VALUE klass) {
  VALUE ary;
  char **list;
  int i, len;

  list = imlib_list_fonts(&len);

  ary = rb_ary_new();
  for (i = 0; i < len; i++)
    rb_ary_push(ary, rb_str_new2(list[i]));

  /* FIXME: there has got to be a better way to do this: */
  imlib_free_font_list(list, len);
  
  return ary;
}

static VALUE font_add_path(VALUE klass, VALUE path) {
  imlib_add_path_to_font_path(STR2CSTR(path));
  return Qtrue;
}

static VALUE font_remove_path(VALUE klass, VALUE path) {
  imlib_remove_path_from_font_path(STR2CSTR(path));
  return Qtrue;
}

static VALUE font_list_paths(VALUE klass) {
  VALUE ary;
  char **list;
  int i, len;

  list = imlib_list_font_path(&len);

  ary = rb_ary_new();
  for (i = 0; i < len; i++)
    rb_ary_push(ary, rb_str_new2(list[i]));

  /* FIXME: there has got to be a better way to do this: */
  imlib_free_font_list(list, len);
  
  return ary;
}

/**********************/
/* GRADIENT FUNCTIONS */
/**********************/
static void gradient_free(void *val) {
  Imlib_Color_Range *range = (Imlib_Color_Range*) val;
  imlib_context_set_color_range(*range);
  imlib_free_color_range();
  free(range);
}

VALUE gradient_new(int argc, VALUE *argv, VALUE klass) {
  Imlib_Color_Range *range;
  VALUE g_o;
  
  range = malloc(sizeof(Imlib_Color_Range*));
  *range = imlib_create_color_range();

  g_o = Data_Wrap_Struct(klass, 0, gradient_free, range);
  rb_obj_call_init(g_o, argc, argv);

  return g_o;
}

static VALUE gradient_add_color(int argc, VALUE *argv, VALUE self) {
  Imlib_Color_Range *grad;
  VALUE color = Qnil;
  int distance;

  switch (argc) {
    case 1:
      distance = NUM2INT(argv[0]);
      break;
    case 2:
      distance = NUM2INT(argv[0]);
      color = argv[1];
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 1 or 2)");
  }

  Data_Get_Struct(self, Imlib_Color_Range, grad);
  imlib_context_set_color_range(*grad);

  if (color != Qnil)
    set_context_color(color);

  imlib_add_color_to_color_range(distance);

  return self;
}

static VALUE gradient_init(int argc, VALUE *argv, VALUE self) {
  VALUE distance, color;
  int i;

  for (i = 0; i < argc; i++) {
    VALUE args[2];
    args[0]= rb_ary_entry(argv[i], 0);
    args[1] = rb_ary_entry(argv[i], 1);

    gradient_add_color(2, args, self);
  }
  
  return self;
}

/*********************/
/* POLYGON FUNCTIONS */
/*********************/
static void poly_free(void *val) {
  ImlibPolygon *poly = (ImlibPolygon*) val;
  imlib_polygon_free(*poly);
  free(poly);
}


VALUE poly_new(int argc, VALUE *argv, VALUE klass) {
  ImlibPolygon *poly;
  VALUE p_o;

  poly = malloc(sizeof(ImlibPolygon*));
  *poly = imlib_polygon_new();

  p_o = Data_Wrap_Struct(klass, 0, poly_free, poly);
  rb_obj_call_init(p_o, argc, argv);

  return p_o;
}

static VALUE poly_add_point(int argc, VALUE *argv, VALUE self) {
  ImlibPolygon *poly;
  int x, y;

  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
      }
      break;
    case 2:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
  }
  
  Data_Get_Struct(self, ImlibPolygon, poly);
  imlib_polygon_add_point(*poly, x, y);
    
  return self;
}

static VALUE poly_init(int argc, VALUE *argv, VALUE self) {
  ImlibPolygon *poly;
  VALUE args[1];
  int i;

  for (i = 0; i < argc; i++) {
    args[0] = argv[i];
    poly_add_point(1, args, self);
  }

  return self;
}

static VALUE poly_bounds(VALUE self) {
  ImlibPolygon *poly;
  VALUE ary;
  int i, r[4] = { 0, 0, 0, 0 };

  Data_Get_Struct(self, ImlibPolygon, poly);
  imlib_polygon_get_bounds(*poly, &r[0], &r[1], &r[2], &r[3]);

  ary = rb_ary_new();
  for (i = 0; i < 4; i++)
    rb_ary_push(ary, INT2FIX(r[i]));

  return ary;
}
  
  
static VALUE poly_contains(int argc, char *argv, VALUE self) {
  ImlibPolygon *poly;
  int x, y;

  switch (argc) {
    case 1:
      switch (TYPE(argv[0])) {
        case T_HASH:
          x = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("x")));
          y = NUM2INT(rb_hash_aref(argv[0], rb_str_new2("y")));
          break;
        case T_ARRAY:
          x = NUM2INT(rb_ary_entry(argv[0], 0));
          y = NUM2INT(rb_ary_entry(argv[0], 1));
          break;
        default:
          rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
      }
      break;
    case 2:
      x = NUM2INT(argv[0]);
      y = NUM2INT(argv[1]);
      break;
    default:
      rb_raise(rb_eTypeError, "invalid argument count (not 2 or 3)");
  }
  
  Data_Get_Struct(self, ImlibPolygon, poly);
  return imlib_polygon_contains_point(*poly, x, y) ? Qtrue : Qfalse;
}

/******************/
/* INIT FUNCTIONS */
/******************/
void setup_color_constants(void) {
  static struct {
    char *name;
    int r, g, b, a;
  } color_constants[] = {
    { "CLEAR",      0,   0,   0,   0   }, 
    { "TRANSPARENT",0,   0,   0,   0   }, 
    { "TRANSLUCENT",0,   0,   0,   0   }, 
    { "SHADOW",     0,   0,   0,   64  },

    { "BLACK",      0,   0,   0,   255 },
    { "DARKGRAY",   64,  64,  64,  255 },
    { "DARKGREY",   64,  64,  64,  255 },
    { "GRAY",       128, 128, 128, 255 },
    { "GREY",       128, 128, 128, 255 },
    { "LIGHTGRAY",  192, 192, 192, 255 },
    { "LIGHTGREY",  192, 192, 192, 255 },
    { "WHITE",      255, 255, 255, 255 },

    { "RED",        255, 0,   0,   255 },
    { "GREEN",      0,   255, 0,   255 },
    { "BLUE",       0,   0,   255, 255 },
    { "YELLOW",     255, 255, 0,   255 },
    { "ORANGE",     255, 128, 0,   255 },
    { "BROWN",      128, 64,  0,   255 },
    { "MAGENTA",    255, 0,   128, 255 },
    { "VIOLET",     255, 0,   255, 255 },
    { "PURPLE",     128, 0,   255, 255 },
    { "INDEGO",     128, 0,   255, 255 },
    { "CYAN",       0,   255, 255, 255 },
    { "AQUA",       0,   128, 255, 255 },
    { "AZURE",      0,   128, 255, 255 },
    { "TEAL",       0,   255, 128, 255 },

    { "DARKRED",    128, 0,   0,   255 },
    { "DARKGREEN",  0,   128, 0,   255 },
    { "DARKBLUE",   0,   0,   128, 255 },
    { "DARKYELLOW", 128, 128, 0,   255 },
    { "DARKORANGE", 128, 64,  0,   255 },
    { "DARKBROWN",  64,  32,  0,   255 },
    { "DARKMAGENTA",128, 0,   64,  255 },
    { "DARKVIOLET", 128, 0,   128, 255 },
    { "DARKPURPLE", 64,  0,   128, 255 },
    { "DARKINDEGO", 64,  0,   128, 255 },
    { "DARKCYAN",   0,   128, 128, 255 },
    { "DARKAQUA",   0,   64,  128, 255 },
    { "DARKAZURE",  0,   64,  128, 255 },
    { "DARKTEAL",   0,   128, 64,  255 },

    { NULL,         0,   0,   0,   0   }
  };
  int i;
  VALUE args[4];

  for (i = 0; color_constants[i].name != NULL; i++) {
    /* fprintf(stderr, "DEBUG: adding %s [%d, %d, %d, %d]\n", 
            color_constants[i].name,
            color_constants[i].r,
            color_constants[i].g,
            color_constants[i].b,
            color_constants[i].a);*/
    args[0] = INT2FIX(color_constants[i].r);
    args[1] = INT2FIX(color_constants[i].g);
    args[2] = INT2FIX(color_constants[i].b);
    args[3] = INT2FIX(color_constants[i].a);
    rb_define_const(mColor,
                    color_constants[i].name,
                    rgba_color_new(4, args, cRgbaColor));
  }
}

void Init_imlib2() {
  /* fprintf(stderr, "DEBUG: Loading Imlib2.so...\n"); */
  mImlib2 = rb_define_module("Imlib2");

  /************************/
  /* define Context class */
  /************************/
  cContext = rb_define_class_under(mImlib2, "Context", rb_cObject);

  /**********************/
  /* define Error class */
  /**********************/
  rb_eval_string(
    "module Imlib2\n"
    "  VERSION = \"" VERSION "\"\n"
    "\n"
    "  class FileError < Exception\n"
    "  end\n"
    "\n"
    "  module Error\n"
    "    class NONE < Imlib2::FileError\nend\n"
    "    class FILE_DOES_NOT_EXIST < Imlib2::FileError\nend\n"
    "    class FILE_IS_DIRECTORY < Imlib2::FileError\nend\n"
    "    class PERMISSION_DENIED_TO_READ < Imlib2::FileError\nend\n"
    "    class NO_LOADER_FOR_FILE_FORMAT < Imlib2::FileError\nend\n"
    "    class PATH_TOO_LONG < Imlib2::FileError\nend\n"
    "    class PATH_COMPONENT_NON_EXISTANT < Imlib2::FileError\nend\n"
    "    class PATH_COMPONENT_NOT_DIRECTORY < Imlib2::FileError\nend\n"
    "    class PATH_POINTS_OUTSIDE_ADDRESS_SPACE < Imlib2::FileError\nend\n"
    "    class TOO_MANY_SYMBOLIC_LINKS < Imlib2::FileError\nend\n"
    "    class OUT_OF_MEMORY < Imlib2::FileError\nend\n"
    "    class OUT_OF_FILE_DESCRIPTORS < Imlib2::FileError\nend\n"
    "    class PERMISSION_DENIED_TO_WRITE < Imlib2::FileError\nend\n"
    "    class OUT_OF_DISK_SPACE < Imlib2::FileError\nend\n"
    "    class UNKNOWN < Imlib2::FileError\nend\n"
    "  end\n"
    "end\n"
  );
  
  /***********************/
  /* define Border class */
  /***********************/
  cBorder = rb_define_class_under(mImlib2, "Border", rb_cObject);
  rb_define_singleton_method(cBorder, "new", border_new, -1);
  rb_define_method(cBorder, "initialize", border_init, -1);
  /* FIXME rb_define_method(cBorder, "[]", border_ary, 0);
  rb_define_method(cBorder, "[]=", border_set_ary, 2); */
  rb_define_method(cBorder, "l", border_left, 0);
  rb_define_method(cBorder, "l=", border_set_left, 1);
  rb_define_method(cBorder, "left", border_left, 0);
  rb_define_method(cBorder, "left=", border_set_left, 1);
  rb_define_method(cBorder, "r", border_right, 0);
  rb_define_method(cBorder, "r=", border_set_right, 1);
  rb_define_method(cBorder, "right", border_right, 0);
  rb_define_method(cBorder, "right=", border_set_right, 1);
  rb_define_method(cBorder, "t", border_top, 0);
  rb_define_method(cBorder, "t=", border_set_top, 1);
  rb_define_method(cBorder, "top", border_top, 0);
  rb_define_method(cBorder, "top=", border_set_top, 1);
  rb_define_method(cBorder, "b", border_bottom, 0);
  rb_define_method(cBorder, "b=", border_set_bottom, 1);
  rb_define_method(cBorder, "bottom", border_bottom, 0);
  rb_define_method(cBorder, "bottom=", border_set_bottom, 1);

  /***********************/
  /* define Cache module */
  /***********************/
  mCache  = rb_define_module_under(mImlib2, "Cache"); 
  rb_define_singleton_method(mCache, "image", cache_image, 0);
  rb_define_singleton_method(mCache, "image=", cache_set_image, 1);
  rb_define_singleton_method(mCache, "image_cache", cache_image, 0);
  rb_define_singleton_method(mCache, "image_cache=", cache_set_image, 1);
  rb_define_singleton_method(mCache, "get_image_cache", cache_image, 0);
  rb_define_singleton_method(mCache, "set_image_cache", cache_set_image, 1);

  rb_define_singleton_method(mCache, "font", cache_font, 0);
  rb_define_singleton_method(mCache, "font=", cache_set_font, 1);
  rb_define_singleton_method(mCache, "font_cache", cache_font, 0);
  rb_define_singleton_method(mCache, "font_cache=", cache_set_font, 1);
  rb_define_singleton_method(mCache, "get_font_cache", cache_font, 0);
  rb_define_singleton_method(mCache, "set_font_cache", cache_set_font, 1);
  rb_define_singleton_method(mCache, "flush_font_cache", cache_flush_font, 0);

  /***********************/
  /* define Color module */
  /***********************/
  mColor  = rb_define_module_under(mImlib2, "Color"); 

  /***************************/
  /* define RGBA Color class */
  /***************************/
  cRgbaColor = rb_define_class_under(mColor, "RgbaColor", rb_cObject);
  rb_define_singleton_method(cRgbaColor, "new", rgba_color_new, -1);
  rb_define_method(cRgbaColor, "initialize", rgba_color_init, -1);
  /* FIXME rb_define_method(cRgbaColor, "[]", rgba_color_ary, 0);
  rb_define_method(cRgbaColor, "[]=", rgba_color_set_ary, 2); */
  rb_define_method(cRgbaColor, "r", rgba_color_red, 0);
  rb_define_method(cRgbaColor, "r=", rgba_color_set_red, 1);
  rb_define_method(cRgbaColor, "red", rgba_color_red, 0);
  rb_define_method(cRgbaColor, "red=", rgba_color_set_red, 1);
  rb_define_method(cRgbaColor, "g", rgba_color_green, 0);
  rb_define_method(cRgbaColor, "g=", rgba_color_set_green, 1);
  rb_define_method(cRgbaColor, "green", rgba_color_green, 0);
  rb_define_method(cRgbaColor, "green=", rgba_color_set_green, 1);
  rb_define_method(cRgbaColor, "b", rgba_color_blue, 0);
  rb_define_method(cRgbaColor, "b=", rgba_color_set_blue, 1);
  rb_define_method(cRgbaColor, "blue", rgba_color_blue, 0);
  rb_define_method(cRgbaColor, "blue=", rgba_color_set_blue, 1);
  rb_define_method(cRgbaColor, "a", rgba_color_alpha, 0);
  rb_define_method(cRgbaColor, "a=", rgba_color_set_alpha, 1);
  rb_define_method(cRgbaColor, "alpha", rgba_color_alpha, 0);
  rb_define_method(cRgbaColor, "alpha=", rgba_color_set_alpha, 1);

  /***************************/
  /* define HSVA Color class */
  /***************************/
  cHsvaColor = rb_define_class_under(mColor, "HsvaColor", rb_cObject);
  rb_define_singleton_method(cHsvaColor, "new", hsva_color_new, -1);
  rb_define_method(cHsvaColor, "initialize", hsva_color_init, -1);
  /* FIXME rb_define_method(cHsvaColor, "[]", hsva_color_ary, 0);
  rb_define_method(cHsvaColor, "[]=", hsva_color_set_ary, 2); */
  rb_define_method(cHsvaColor, "h", hsva_color_hue, 0);
  rb_define_method(cHsvaColor, "h=", hsva_color_set_hue, 1);
  rb_define_method(cHsvaColor, "hue", hsva_color_hue, 0);
  rb_define_method(cHsvaColor, "hue=", hsva_color_set_hue, 1);
  rb_define_method(cHsvaColor, "s", hsva_color_saturation, 0);
  rb_define_method(cHsvaColor, "s=", hsva_color_set_saturation, 1);
  rb_define_method(cHsvaColor, "saturation", hsva_color_saturation, 0);
  rb_define_method(cHsvaColor, "saturation=", hsva_color_set_saturation, 1);
  rb_define_method(cHsvaColor, "v", hsva_color_value, 0);
  rb_define_method(cHsvaColor, "v=", hsva_color_set_value, 1);
  rb_define_method(cHsvaColor, "value", hsva_color_value, 0);
  rb_define_method(cHsvaColor, "value=", hsva_color_set_value, 1);
  rb_define_method(cHsvaColor, "a", hsva_color_alpha, 0);
  rb_define_method(cHsvaColor, "a=", hsva_color_set_alpha, 1);
  rb_define_method(cHsvaColor, "alpha", hsva_color_alpha, 0);
  rb_define_method(cHsvaColor, "alpha=", hsva_color_set_alpha, 1);

  /***************************/
  /* define HSVA Color class */
  /***************************/
  cHlsaColor = rb_define_class_under(mColor, "HlsaColor", rb_cObject);
  rb_define_singleton_method(cHlsaColor, "new", hlsa_color_new, -1);
  rb_define_method(cHlsaColor, "initialize", hlsa_color_init, -1);
  /* FIXME rb_define_method(cHlsaColor, "[]", hlsa_color_ary, 0);
  rb_define_method(cHlsaColor, "[]=", hlsa_color_set_ary, 2); */
  rb_define_method(cHlsaColor, "h", hlsa_color_hue, 0);
  rb_define_method(cHlsaColor, "h=", hlsa_color_set_hue, 1);
  rb_define_method(cHlsaColor, "hue", hlsa_color_hue, 0);
  rb_define_method(cHlsaColor, "hue=", hlsa_color_set_hue, 1);
  rb_define_method(cHlsaColor, "l", hlsa_color_lightness, 0);
  rb_define_method(cHlsaColor, "l=", hlsa_color_set_lightness, 1);
  rb_define_method(cHlsaColor, "lightness", hlsa_color_lightness, 0);
  rb_define_method(cHlsaColor, "lightness=", hlsa_color_set_lightness, 1);
  rb_define_method(cHlsaColor, "s", hlsa_color_saturation, 0);
  rb_define_method(cHlsaColor, "s=", hlsa_color_set_saturation, 1);
  rb_define_method(cHlsaColor, "saturation", hlsa_color_saturation, 0);
  rb_define_method(cHlsaColor, "saturation=", hlsa_color_set_saturation, 1);
  rb_define_method(cHlsaColor, "a", hlsa_color_alpha, 0);
  rb_define_method(cHlsaColor, "a=", hlsa_color_set_alpha, 1);
  rb_define_method(cHlsaColor, "alpha", hlsa_color_alpha, 0);
  rb_define_method(cHlsaColor, "alpha=", hlsa_color_set_alpha, 1);

  /***************************/
  /* define CMYA Color class */
  /***************************/
  cCmyaColor = rb_define_class_under(mColor, "CmyaColor", rb_cObject);
  rb_define_singleton_method(cCmyaColor, "new", cmya_color_new, -1);
  rb_define_method(cCmyaColor, "initialize", cmya_color_init, -1);
  /* FIXME rb_define_method(cCmyaColor, "[]", cmya_color_ary, 0);
  rb_define_method(cCmyaColor, "[]=", cmya_color_set_ary, 2); */
  rb_define_method(cCmyaColor, "c", cmya_color_cyan, 0);
  rb_define_method(cCmyaColor, "c=", cmya_color_set_cyan, 1);
  rb_define_method(cCmyaColor, "cyan", cmya_color_cyan, 0);
  rb_define_method(cCmyaColor, "cyan=", cmya_color_set_cyan, 1);
  rb_define_method(cCmyaColor, "m", cmya_color_magenta, 0);
  rb_define_method(cCmyaColor, "m=", cmya_color_set_magenta, 1);
  rb_define_method(cCmyaColor, "magenta", cmya_color_magenta, 0);
  rb_define_method(cCmyaColor, "magenta=", cmya_color_set_magenta, 1);
  rb_define_method(cCmyaColor, "y", cmya_color_yellow, 0);
  rb_define_method(cCmyaColor, "y=", cmya_color_set_yellow, 1);
  rb_define_method(cCmyaColor, "yellow", cmya_color_yellow, 0);
  rb_define_method(cCmyaColor, "yellow=", cmya_color_set_yellow, 1);
  rb_define_method(cCmyaColor, "a", cmya_color_alpha, 0);
  rb_define_method(cCmyaColor, "a=", cmya_color_set_alpha, 1);
  rb_define_method(cCmyaColor, "alpha", cmya_color_alpha, 0);
  rb_define_method(cCmyaColor, "alpha=", cmya_color_set_alpha, 1);

  /**************************/
  /* define Color constants */
  /**************************/
  setup_color_constants();
  
  /*************************/
  /* define Gradient class */
  /*************************/
  cGradient = rb_define_class_under(mImlib2, "Gradient", rb_cObject);
  rb_define_singleton_method(cGradient, "new", gradient_new, -1);
  rb_define_method(cGradient, "initialize", gradient_init, -1);
  rb_define_method(cGradient, "add_color", gradient_add_color, 2);

  /* hack: alias should work with modules and classes as well :) */
  rb_eval_string(
    "module Imlib2\n"
    "  class ColorRange < Gradient\n"
    "  end\n"
    "end\n"
  );

  /**********************/
  /* define Image class */
  /**********************/
  cImage   = rb_define_class_under(mImlib2, "Image", rb_cObject);
  rb_define_singleton_method(cImage, "new", image_new, 2);
  rb_define_method(cImage, "initialize", image_initialize, 0);

  /* create methods */
  rb_define_singleton_method(cImage, "create", image_new, 2);
  rb_define_singleton_method(cImage, "create_using_data", image_create_using_data, 2);
  rb_define_singleton_method(cImage, "create_using_copied_data", image_create_using_copied_data, 2);

  /* load methods */
  rb_define_singleton_method(cImage, "load", image_load, 1);
  rb_define_singleton_method(cImage, "load_image", image_load_image, 1);
  rb_define_singleton_method(cImage, "load_immediately", image_load_immediately, 1);
  rb_define_singleton_method(cImage, "load_without_cache", image_load_without_cache, 1);
  rb_define_singleton_method(cImage, "load_immediately_without_cache", image_load_immediately_without_cache, 1);
  rb_define_singleton_method(cImage, "load_with_error_return", image_load_with_error_return, 1);

  /* save methods */
  rb_define_method(cImage, "save", image_save, 1);
  rb_define_method(cImage, "save_image", image_save_image, 1);
  rb_define_method(cImage, "save_with_error_return", image_save_with_error_return, 1);

  /* member methods */
  rb_define_method(cImage, "width", image_width, 0);
  rb_define_method(cImage, "height", image_height, 0);
  rb_define_method(cImage, "filename", image_filename, 0);

  rb_define_method(cImage, "data", image_data, 0);
  rb_define_method(cImage, "data_for_reading_only", image_data_ro, 0);
  rb_define_method(cImage, "data!", image_data_ro, 0);
  rb_define_method(cImage, "data=", image_put_data, 1);
  rb_define_method(cImage, "put_back_data", image_put_data, 1);

  rb_define_method(cImage, "has_alpha", image_has_alpha, 0);
  rb_define_method(cImage, "has_alpha?", image_has_alpha, 0);
  rb_define_method(cImage, "has_alpha=", image_set_has_alpha, 1);
  rb_define_method(cImage, "set_has_alpha", image_set_has_alpha, 1);

  rb_define_method(cImage, "changes_on_disk", image_changes_on_disk, 0);
  rb_define_method(cImage, "set_changes_on_disk", image_changes_on_disk, 0);

  rb_define_method(cImage, "border", image_get_border, 0);
  rb_define_method(cImage, "get_border", image_get_border, 0);
  rb_define_method(cImage, "border=", image_set_border, 1);
  rb_define_method(cImage, "set_border", image_set_border, 1);

  rb_define_method(cImage, "format", image_get_format, 0);
  rb_define_method(cImage, "get_format", image_get_format, 0);
  rb_define_method(cImage, "format=", image_set_format, 1);
  rb_define_method(cImage, "set_format", image_set_format, 1);

  rb_define_method(cImage, "irrelevant_format=", image_irrelevant_format, 1);
  rb_define_method(cImage, "set_irrelevant_format", image_irrelevant_format, 1);
  rb_define_method(cImage, "irrelevant_border=", image_irrelevant_border, 1);
  rb_define_method(cImage, "set_irrelevant_border", image_irrelevant_border, 1);
  rb_define_method(cImage, "irrelevant_alpha=", image_irrelevant_alpha, 1);
  rb_define_method(cImage, "set_irrelevant_alpha", image_irrelevant_alpha, 1);

  rb_define_method(cImage, "query", image_query_pixel, 2);
  rb_define_method(cImage, "query_rgba", image_query_pixel, 2);
  rb_define_method(cImage, "query_pixel", image_query_pixel, 2);
  rb_define_method(cImage, "query_hsva", image_query_pixel_hsva, 2);
  rb_define_method(cImage, "query_pixel_hsva", image_query_pixel_hsva, 2);
  rb_define_method(cImage, "query_hlsa", image_query_pixel_hlsa, 2);
  rb_define_method(cImage, "query_pixel_hlsa", image_query_pixel_hlsa, 2);
  rb_define_method(cImage, "query_cmya", image_query_pixel_cmya, 2);
  rb_define_method(cImage, "query_pixel_cmya", image_query_pixel_cmya, 2);

  /* more create methods */
  rb_define_method(cImage, "crop", image_crop, -1);
  rb_define_method(cImage, "create_cropped", image_crop, -1);
  rb_define_method(cImage, "crop!", image_crop_inline, -1);
  rb_define_method(cImage, "create_cropped!", image_crop_inline, -1);
  rb_define_method(cImage, "crop_scaled", image_crop_scaled, -1);
  rb_define_method(cImage, "create_cropped_scaled", image_crop_scaled, -1);
  rb_define_method(cImage, "crop_scaled!", image_crop_scaled_inline, -1);
  rb_define_method(cImage, "create_cropped_scaled!", image_crop_scaled_inline, -1);

  /* image modification methods */
  rb_define_method(cImage, "flip_horizontal", image_flip_horizontal, 0);
  rb_define_method(cImage, "flip_horizontal!", image_flip_horizontal_inline, 0);
  rb_define_method(cImage, "flip_vertical", image_flip_vertical, 0);
  rb_define_method(cImage, "flip_vertical!", image_flip_vertical_inline, 0);
  rb_define_method(cImage, "flip_diagonal", image_flip_diagonal, 0);
  rb_define_method(cImage, "flip_diagonal!", image_flip_diagonal_inline, 0);

  rb_define_method(cImage, "orientate", image_orientate, 1);
  rb_define_method(cImage, "orientate!", image_orientate_inline, 1);
  rb_define_method(cImage, "blur", image_blur, 1);
  rb_define_method(cImage, "blur!", image_blur_inline, 1);
  rb_define_method(cImage, "sharpen", image_blur, 1);
  rb_define_method(cImage, "sharpen!", image_blur_inline, 1);

  rb_define_method(cImage, "tile_horizontal", image_tile_horizontal, 0);
  rb_define_method(cImage, "tile_horizontal!", image_tile_horizontal_inline, 0);
  rb_define_method(cImage, "tile_vertical", image_tile_vertical, 0);
  rb_define_method(cImage, "tile_vertical!", image_tile_vertical_inline, 0);
  rb_define_method(cImage, "tile", image_tile, 0);
  rb_define_method(cImage, "tile!", image_tile_inline, 0);

  /* image drawing methods */
  rb_define_method(cImage, "draw_pixel", image_draw_pixel, -1);
  rb_define_method(cImage, "draw_line", image_draw_line, -1);
  /* FIXME: rb_define_method(cImage, "clip_line", image_clip_line, -1);*/
  rb_define_method(cImage, "draw_rect", image_draw_rect, -1);
  rb_define_method(cImage, "draw_rectangle", image_draw_rect, -1);
  rb_define_method(cImage, "fill_rect", image_fill_rect, -1);
  rb_define_method(cImage, "fill_rectangle", image_fill_rect, -1);
  rb_define_method(cImage, "copy_alpha", image_copy_alpha, -1);
  rb_define_method(cImage, "copy_alpha_rect", image_copy_alpha_rect, -1);
  rb_define_method(cImage, "scroll_rect", image_scroll_rect, -1);
  rb_define_method(cImage, "copy_rect", image_copy_rect, -1);

  /* ellipse drawing methods */
  rb_define_method(cImage, "draw_ellipse", image_draw_ellipse, -1);
  rb_define_method(cImage, "draw_oval", image_draw_ellipse, -1);
  rb_define_method(cImage, "fill_ellipse", image_fill_ellipse, -1);
  rb_define_method(cImage, "fill_oval", image_fill_ellipse, -1);

  /* text drawing methods */
  rb_define_method(cImage, "draw_text", image_draw_text, -1);

  /* gradient (color range) drawing methods */
  rb_define_method(cImage, "gradient", image_fill_gradient, -1);
  rb_define_method(cImage, "fill_gradient", image_fill_gradient, -1);
  rb_define_method(cImage, "color_range", image_fill_gradient, -1);
  rb_define_method(cImage, "fill_color_range", image_fill_gradient, -1);

  /* polygon drawing methods */
  rb_define_method(cImage, "draw_poly", image_draw_poly, -1);
  rb_define_method(cImage, "draw_polygon", image_draw_poly, -1);
  rb_define_method(cImage, "fill_poly", image_fill_poly, -1);
  rb_define_method(cImage, "fill_polygon", image_fill_poly, -1);

  /* blend methods */
  rb_define_method(cImage, "blend!", image_blend_image_inline, -1);
  rb_define_method(cImage, "blend_image!", image_blend_image_inline, -1);
  rb_define_method(cImage, "blend", image_blend_image, -1);
  rb_define_method(cImage, "blend_image", image_blend_image, -1);

  /* rotation / skewing methods */
  rb_define_method(cImage, "rotate", image_rotate, 1);
  rb_define_method(cImage, "rotate!", image_rotate_inline, 1);

  /* misc methods */
  rb_define_method(cImage, "clone", image_clone, 0);
  rb_define_method(cImage, "dup", image_clone, 0);

  /* clear methods */
  rb_define_method(cImage, "clear", image_clear, 0);
  rb_define_method(cImage, "clear_color", image_clear_color, 1);
  rb_define_method(cImage, "clear_color!", image_clear_color_inline, 1);

  /*********************/
  /* define Font class */
  /*********************/
  cFont    = rb_define_class_under(mImlib2, "Font", rb_cObject);
  rb_define_singleton_method(cFont, "new", font_new, 1);
  rb_define_singleton_method(cFont, "load", font_new, 1);
  rb_define_method(cFont, "initialize", font_init, 0);

  rb_define_method(cFont, "size", font_text_size, 1);
  rb_define_method(cFont, "text_size", font_text_size, 1);
  rb_define_method(cFont, "get_text_size", font_text_size, 1);
  rb_define_method(cFont, "advance", font_text_advance, 1);
  rb_define_method(cFont, "text_advance", font_text_advance, 1);
  rb_define_method(cFont, "get_text_advance", font_text_advance, 1);
  rb_define_method(cFont, "inset", font_text_inset, 1);
  rb_define_method(cFont, "text_inset", font_text_inset, 1);
  rb_define_method(cFont, "get_text_inset", font_text_inset, 1);

  rb_define_method(cFont, "index", font_text_index, -1);
  rb_define_method(cFont, "text_index", font_text_index, -1);
  rb_define_method(cFont, "text_index_and_location", font_text_index, -1);
  rb_define_method(cFont, "get_text_index_and_location", font_text_index, -1);
  rb_define_method(cFont, "location", font_text_location, 2);
  rb_define_method(cFont, "text_location", font_text_location, 2);
  rb_define_method(cFont, "text_location_at_index", font_text_location, 2);
  rb_define_method(cFont, "get_text_location_at_index", font_text_location, 2);

  rb_define_method(cFont, "ascent", font_ascent, 0);
  rb_define_method(cFont, "get_ascent", font_ascent, 0);
  rb_define_method(cFont, "descent", font_descent, 0);
  rb_define_method(cFont, "get_descent", font_descent, 0);
  rb_define_method(cFont, "maximum_ascent", font_maximum_ascent, 0);
  rb_define_method(cFont, "get_maximum_ascent", font_maximum_ascent, 0);
  rb_define_method(cFont, "maximum_descent", font_maximum_descent, 0);
  rb_define_method(cFont, "get_maximum_descent", font_maximum_descent, 0);

  /*******************************/
  /* define Font singletons      */
  /* (font list, and font paths) */
  /*******************************/
  rb_define_singleton_method(cFont, "list", font_list_fonts, 0);
  rb_define_singleton_method(cFont, "fonts", font_list_fonts, 0);
  rb_define_singleton_method(cFont, "list_fonts", font_list_fonts, 0);

  rb_define_singleton_method(cFont, "add_path", font_add_path, 1);
  rb_define_singleton_method(cFont, "remove_path", font_remove_path, 1);
  rb_define_singleton_method(cFont, "paths", font_list_paths, 0);
  rb_define_singleton_method(cFont, "list_paths", font_list_paths, 0);

  /*********************/
  /* define Font class */
  /*********************/
  cPolygon = rb_define_class_under(mImlib2, "Polygon", rb_cObject);
  rb_define_singleton_method(cPolygon, "new", poly_new, -1);
  rb_define_method(cPolygon, "initialize", poly_init, -1);
  rb_define_method(cPolygon, "add_point", poly_add_point, -1);
  rb_define_method(cPolygon, "bounds", poly_bounds, 0);
  rb_define_method(cPolygon, "get_bounds", poly_bounds, 0);
  rb_define_method(cPolygon, "contains?", poly_contains, -1);
  rb_define_method(cPolygon, "contains_point?", poly_contains, -1);

  /* fprintf(stderr, "DEBUG: Done Loading Imlib2.so.\n"); */
}

