]> git.imager.perl.org - imager.git/blobdiff - Imager.xs
avoid re-entrancy into giflib using the mutex API
[imager.git] / Imager.xs
index e11fd0316b46eed8736566e577274ced6bb68daf..e9e6351997e93485c3e6eb4f76bdb8943feff61f 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -7,6 +7,7 @@ extern "C" {
 #include "XSUB.h"
 #define NEED_newRV_noinc
 #define NEED_sv_2pv_nolen
+#define NEED_sv_2pvbyte
 #include "ppport.h"
 #ifdef __cplusplus
 }
@@ -30,6 +31,85 @@ extern "C" {
 
 /*
 
+Context object management
+
+*/
+
+typedef im_context_t Imager__Context;
+
+#define im_context_DESTROY(ctx) im_context_refdec((ctx), "DESTROY")
+
+#ifdef PERL_IMPLICIT_CONTEXT
+
+#define MY_CXT_KEY "Imager::_context" XS_VERSION
+
+typedef struct {
+  im_context_t ctx;
+} my_cxt_t;
+
+START_MY_CXT
+
+im_context_t fallback_context;
+
+static void
+start_context(pTHX) {
+  dMY_CXT;
+  MY_CXT.ctx = im_context_new();
+  sv_setref_pv(get_sv("Imager::_context", GV_ADD), "Imager::Context", MY_CXT.ctx);
+
+  /* Ideally we'd free this reference, but the error message memory
+     was never released on exit, so the associated memory here is reasonable
+     to keep.
+     With logging enabled we always need at least one context, since
+     objects may be released fairly late and attempt to get the log file.
+  */
+  im_context_refinc(MY_CXT.ctx, "start_context");
+  fallback_context = MY_CXT.ctx;
+}
+
+static im_context_t
+perl_get_context(void) {
+  dTHX;
+  dMY_CXT;
+  
+  return MY_CXT.ctx ? MY_CXT.ctx : fallback_context;
+}
+
+#else
+
+static im_context_t perl_context;
+
+static void
+start_context(pTHX) {
+  perl_context = im_context_new();
+  im_context_refinc(perl_context, "start_context");
+}
+
+static im_context_t
+perl_get_context(void) {
+  return perl_context;
+}
+
+#endif
+
+/* used to represent channel lists parameters */
+typedef struct i_channel_list_tag {
+  int *channels;
+  int count;
+} i_channel_list;
+
+typedef struct {
+  size_t count;
+  const i_sample_t *samples;
+} i_sample_list;
+
+typedef struct {
+  size_t count;
+  const i_fsample_t *samples;
+} i_fsample_list;
+
+/*
+
 Allocate memory that will be discarded when mortals are discarded.
 
 */
@@ -131,9 +211,19 @@ void my_SvREFCNT_dec(void *p) {
 
 static void
 i_log_entry(char *string, int level) {
-  mm_log((level, string));
+  mm_log((level, "%s", string));
 }
 
+static SV *
+make_i_color_sv(pTHX_ const i_color *c) {
+  SV *sv;
+  i_color *col = mymalloc(sizeof(i_color));
+  *col = *c;
+  sv = sv_newmortal();
+  sv_setref_pv(sv, "Imager::Color", (void *)col);
+
+  return sv;
+}
 
 #define CBDATA_BUFSIZE 8192
 
@@ -143,81 +233,22 @@ struct cbdata {
   SV *readcb;
   SV *seekcb;
   SV *closecb;
-
-  /* we need to remember whether the buffer contains write data or 
-     read data
-   */
-  int reading;
-  int writing;
-
-  /* how far we've read into the buffer (not used for writing) */
-  int where;
-
-  /* the amount of space used/data available in the buffer */
-  int used;
-
-  /* the maximum amount to fill the buffer before flushing
-     If any write is larger than this then the buffer is flushed and 
-     the full write is performed.  The write is _not_ split into 
-     maxwrite sized calls
-   */
-  int maxlength;
-
-  char buffer[CBDATA_BUFSIZE];
 };
 
-/* 
-
-call_writer(cbd, buf, size)
-
-Low-level function to call the perl writer callback.
-
-*/
-
-static ssize_t call_writer(struct cbdata *cbd, void const *buf, size_t size) {
-  dTHX;
-  int count;
-  int success;
-  SV *sv;
-  dSP;
-
-  if (!SvOK(cbd->writecb))
-    return -1;
-
-  ENTER;
-  SAVETMPS;
-  EXTEND(SP, 1);
-  PUSHMARK(SP);
-  PUSHs(sv_2mortal(newSVpv((char *)buf, size)));
-  PUTBACK;
-
-  count = perl_call_sv(cbd->writecb, G_SCALAR);
-
-  SPAGAIN;
-  if (count != 1)
-    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
-
-  sv = POPs;
-  success = SvTRUE(sv);
-
-
-  PUTBACK;
-  FREETMPS;
-  LEAVE;
-
-  return success ? size : -1;
-}
-
-static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size, 
-                           size_t maxread) {
+static ssize_t
+call_reader(struct cbdata *cbd, void *buf, size_t size, 
+            size_t maxread) {
   dTHX;
   int count;
   int result;
   SV *data;
   dSP;
 
-  if (!SvOK(cbd->readcb))
+  if (!SvOK(cbd->readcb)) {
+    mm_log((1, "read callback called but no readcb supplied\n"));
+    i_push_error(0, "read callback called but no readcb supplied");
     return -1;
+  }
 
   ENTER;
   SAVETMPS;
@@ -238,9 +269,10 @@ static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
 
   if (SvOK(data)) {
     STRLEN len;
-    char *ptr = SvPV(data, len);
+    char *ptr = SvPVbyte(data, len);
     if (len > maxread)
-      croak("Too much data returned in reader callback");
+      croak("Too much data returned in reader callback (wanted %d, got %d, expected %d)",
+      (int)size, (int)len, (int)maxread);
     
     memcpy(buf, ptr, len);
     result = len;
@@ -256,41 +288,20 @@ static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
   return result;
 }
 
-static ssize_t write_flush(struct cbdata *cbd) {
-  dTHX;
-  ssize_t result;
-
-  if (cbd->used) {
-    result = call_writer(cbd, cbd->buffer, cbd->used);
-    cbd->used = 0;
-    return result;
-  }
-  else {
-    return 1; /* success of some sort */
-  }
-}
-
-static off_t io_seeker(void *p, off_t offset, int whence) {
+static off_t
+io_seeker(void *p, off_t offset, int whence) {
   dTHX;
   struct cbdata *cbd = p;
   int count;
   off_t result;
   dSP;
 
-  if (!SvOK(cbd->seekcb))
+  if (!SvOK(cbd->seekcb)) {
+    mm_log((1, "seek callback called but no seekcb supplied\n"));
+    i_push_error(0, "seek callback called but no seekcb supplied");
     return -1;
-
-  if (cbd->writing) {
-    if (cbd->used && write_flush(cbd) <= 0)
-      return -1;
-    cbd->writing = 0;
-  }
-  if (whence == SEEK_CUR && cbd->reading && cbd->where != cbd->used) {
-    offset -= cbd->where - cbd->used;
   }
-  cbd->reading = 0;
-  cbd->where = cbd->used = 0;
-  
+
   ENTER;
   SAVETMPS;
   EXTEND(SP, 2);
@@ -315,126 +326,80 @@ static off_t io_seeker(void *p, off_t offset, int whence) {
   return result;
 }
 
-static ssize_t io_writer(void *p, void const *data, size_t size) {
+static ssize_t
+io_writer(void *p, void const *data, size_t size) {
   dTHX;
   struct cbdata *cbd = p;
+  I32 count;
+  SV *sv;
+  dSP;
+  bool success;
 
-  /* printf("io_writer(%p, %p, %u)\n", p, data, size); */
-  if (!cbd->writing) {
-    if (cbd->reading && cbd->where < cbd->used) {
-      /* we read past the place where the caller expected us to be
-         so adjust our position a bit */
-      if (io_seeker(p, cbd->where - cbd->used, SEEK_CUR) < 0) {
-        return -1;
-      }
-      cbd->reading = 0;
-    }
-    cbd->where = cbd->used = 0;
-  }
-  cbd->writing = 1;
-  if (cbd->used && cbd->used + size > cbd->maxlength) {
-    int write_res = write_flush(cbd);
-    if (write_res <= 0) {
-      return write_res;
-    }
-    cbd->used = 0;
-  }
-  if (cbd->used+size <= cbd->maxlength) {
-    memcpy(cbd->buffer + cbd->used, data, size);
-    cbd->used += size;
-    return size;
+  if (!SvOK(cbd->writecb)) {
+    mm_log((1, "write callback called but no writecb supplied\n"));
+    i_push_error(0, "write callback called but no writecb supplied");
+    return -1;
   }
-  /* it doesn't fit - just pass it up */
-  return call_writer(cbd, data, size);
+
+  ENTER;
+  SAVETMPS;
+  EXTEND(SP, 1);
+  PUSHMARK(SP);
+  PUSHs(sv_2mortal(newSVpv((char *)data, size)));
+  PUTBACK;
+
+  count = perl_call_sv(cbd->writecb, G_SCALAR);
+
+  SPAGAIN;
+  if (count != 1)
+    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+  sv = POPs;
+  success = SvTRUE(sv);
+
+
+  PUTBACK;
+  FREETMPS;
+  LEAVE;
+
+  return success ? size : -1;
 }
 
 static ssize_t 
 io_reader(void *p, void *data, size_t size) {
-  dTHX;
   struct cbdata *cbd = p;
-  ssize_t total;
-  char *out = data; /* so we can do pointer arithmetic */
-
-  /* printf("io_reader(%p, %p, %d)\n", p, data, size); */
-  if (cbd->writing) {
-    if (write_flush(cbd) <= 0)
-      return 0;
-    cbd->writing = 0;
-  }
 
-  cbd->reading = 1;
-  if (size <= cbd->used - cbd->where) {
-    /* simplest case */
-    memcpy(data, cbd->buffer+cbd->where, size);
-    cbd->where += size;
-    return size;
-  }
-  total = 0;
-  memcpy(out, cbd->buffer + cbd->where, cbd->used - cbd->where);
-  total += cbd->used - cbd->where;
-  size  -= cbd->used - cbd->where;
-  out   += cbd->used - cbd->where;
-  if (size < sizeof(cbd->buffer)) {
-    int did_read = 0;
-    int copy_size;
-    while (size
-          && (did_read = call_reader(cbd, cbd->buffer, size, 
-                                   sizeof(cbd->buffer))) > 0) {
-      cbd->where = 0;
-      cbd->used  = did_read;
-
-      copy_size = i_min(size, cbd->used);
-      memcpy(out, cbd->buffer, copy_size);
-      cbd->where += copy_size;
-      out   += copy_size;
-      total += copy_size;
-      size  -= copy_size;
-    }
-    if (did_read < 0)
-      return -1;
-  }
-  else {
-    /* just read the rest - too big for our buffer*/
-    int did_read;
-    while ((did_read = call_reader(cbd, out, size, size)) > 0) {
-      size  -= did_read;
-      total += did_read;
-      out   += did_read;
-    }
-    if (did_read < 0)
-      return -1;
-  }
-
-  return total;
+  return call_reader(cbd, data, size, size);
 }
 
 static int io_closer(void *p) {
   dTHX;
   struct cbdata *cbd = p;
-
-  if (cbd->writing && cbd->used > 0) {
-    if (write_flush(cbd) < 0)
-      return -1;
-    cbd->writing = 0;
-  }
+  int success = 1;
 
   if (SvOK(cbd->closecb)) {
     dSP;
+    I32 count;
+    SV *sv;
 
     ENTER;
     SAVETMPS;
     PUSHMARK(SP);
     PUTBACK;
 
-    perl_call_sv(cbd->closecb, G_VOID);
+    count = perl_call_sv(cbd->closecb, G_SCALAR);
 
     SPAGAIN;
+    
+    sv = POPs;
+    success = SvTRUE(sv);
+
     PUTBACK;
     FREETMPS;
     LEAVE;
   }
 
-  return 0;
+  return success ? 0 : -1;
 }
 
 static void io_destroyer(void *p) {
@@ -448,6 +413,53 @@ static void io_destroyer(void *p) {
   myfree(cbd);
 }
 
+static i_io_glue_t *
+do_io_new_buffer(pTHX_ SV *data_sv) {
+  const char *data;
+  STRLEN length;
+
+  data = SvPVbyte(data_sv, length);
+  SvREFCNT_inc(data_sv);
+  return io_new_buffer(data, length, my_SvREFCNT_dec, data_sv);
+}
+
+static const char *
+describe_sv(SV *sv) {
+  if (SvOK(sv)) {
+    if (SvROK(sv)) {
+      svtype type = SvTYPE(SvRV(sv));
+      switch (type) {
+      case SVt_PVCV: return "CV";
+      case SVt_PVGV: return "GV";
+      case SVt_PVLV: return "LV";
+      default: return "some reference";
+      }
+    }
+    else {
+      return "non-reference scalar";
+    }
+  }
+  else {
+    return "undef";
+  }
+}
+
+static i_io_glue_t *
+do_io_new_cb(pTHX_ SV *writecb, SV *readcb, SV *seekcb, SV *closecb) {
+  struct cbdata *cbd;
+
+  cbd = mymalloc(sizeof(struct cbdata));
+  cbd->writecb = newSVsv(writecb);
+  cbd->readcb = newSVsv(readcb);
+  cbd->seekcb = newSVsv(seekcb);
+  cbd->closecb = newSVsv(closecb);
+
+  mm_log((1, "do_io_new_cb(writecb %p (%s), readcb %p (%s), seekcb %p (%s), closecb %p (%s))\n", writecb, describe_sv(writecb), readcb, describe_sv(readcb), seekcb, describe_sv(seekcb), closecb, describe_sv(closecb)));
+
+  return io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, 
+                  io_destroyer);
+}
+
 struct value_name {
   char *name;
   int value;
@@ -477,6 +489,9 @@ static struct value_name make_color_names[] =
   { "mediancut", mc_median_cut, },
   { "mono", mc_mono, },
   { "monochrome", mc_mono, },
+  { "gray", mc_gray, },
+  { "gray4", mc_gray4, },
+  { "gray16", mc_gray16, },
 };
 
 static struct value_name translate_names[] =
@@ -775,7 +790,6 @@ validate_i_ppal(i_img *im, i_palidx const *indexes, int count) {
   }
 }
 
-
 /* I don't think ICLF_* names belong at the C interface
    this makes the XS code think we have them, to let us avoid 
    putting function bodies in the XS code
@@ -883,6 +897,9 @@ static im_pl_ext_funcs im_perl_funcs =
 #define PERL_PL_SET_GLOBAL_CALLBACKS \
   sv_setiv(get_sv(PERL_PL_FUNCTION_TABLE_NAME, 1), PTR2IV(&im_perl_funcs));
 
+#define IIM_new i_img_8_new
+#define IIM_DESTROY i_img_destroy
+
 #ifdef IMEXIF_ENABLE
 #define i_exif_enabled() 1
 #else
@@ -897,6 +914,9 @@ static im_pl_ext_funcs im_perl_funcs =
 
 #define i_img_epsilonf() (DBL_EPSILON * 4)
 
+/* avoid some xsubpp strangeness */
+#define NEWLINE '\n'
+
 MODULE = Imager                PACKAGE = Imager::Color PREFIX = ICL_
 
 Imager::Color
@@ -1048,14 +1068,10 @@ io_new_bufchain()
 
 
 Imager::IO
-io_new_buffer(data)
-         char   *data
-       PREINIT:
-         size_t length;
+io_new_buffer(data_sv)
+         SV   *data_sv
        CODE:
-         SvPV(ST(0), length);
-          SvREFCNT_inc(ST(0));
-         RETVAL = io_new_buffer(data, length, my_SvREFCNT_dec, ST(0));
+         RETVAL = do_io_new_buffer(aTHX_ data_sv);
         OUTPUT:
           RETVAL
 
@@ -1066,39 +1082,24 @@ io_new_cb(writecb, readcb, seekcb, closecb, maxwrite = CBDATA_BUFSIZE)
         SV *seekcb;
         SV *closecb;
         int maxwrite;
-      PREINIT:
-        struct cbdata *cbd;
       CODE:
-        cbd = mymalloc(sizeof(struct cbdata));
-        SvREFCNT_inc(writecb);
-        cbd->writecb = writecb;
-        SvREFCNT_inc(readcb);
-        cbd->readcb = readcb;
-        SvREFCNT_inc(seekcb);
-        cbd->seekcb = seekcb;
-        SvREFCNT_inc(closecb);
-        cbd->closecb = closecb;
-        cbd->reading = cbd->writing = cbd->where = cbd->used = 0;
-        if (maxwrite > CBDATA_BUFSIZE)
-          maxwrite = CBDATA_BUFSIZE;
-        cbd->maxlength = maxwrite;
-        RETVAL = io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, 
-                           io_destroyer);
+        RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
       OUTPUT:
         RETVAL
 
-void
+SV *
 io_slurp(ig)
         Imager::IO     ig
             PREINIT:
              unsigned char*    data;
              size_t    tlength;
-            PPCODE:
+            CODE:
              data    = NULL;
               tlength = io_slurp(ig, &data);
-              EXTEND(SP,1);
-              PUSHs(sv_2mortal(newSVpv((char *)data,tlength)));
+              RETVAL = newSVpv((char *)data,tlength);
               myfree(data);
+            OUTPUT:
+             RETVAL
 
 
 undef_int
@@ -1120,10 +1121,68 @@ i_get_image_file_limits()
           PUSHs(sv_2mortal(newSVuv(bytes)));
         }
 
+bool
+i_int_check_image_file_limits(width, height, channels, sample_size)
+       i_img_dim width
+       i_img_dim height
+       int channels
+       size_t sample_size
+  PROTOTYPE: DISABLE
+
+MODULE = Imager                PACKAGE = Imager::IO    PREFIX = io_
+
+Imager::IO
+io_new_fd(class, fd)
+       int fd
+    CODE:
+       RETVAL = io_new_fd(fd);
+    OUTPUT:
+       RETVAL
+
+Imager::IO
+io_new_buffer(class, data_sv)
+       SV *data_sv
+    CODE:
+        RETVAL = do_io_new_buffer(aTHX_ data_sv);
+    OUTPUT:
+        RETVAL
+
+Imager::IO
+io_new_cb(class, writecb, readcb, seekcb, closecb)
+        SV *writecb;
+        SV *readcb;
+        SV *seekcb;
+        SV *closecb;
+    CODE:
+        RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
+    OUTPUT:
+        RETVAL
+
+Imager::IO
+io_new_bufchain(class)
+    CODE:
+       RETVAL = io_new_bufchain();
+    OUTPUT:
+        RETVAL
+
+SV *
+io_slurp(class, ig)
+        Imager::IO     ig
+    PREINIT:
+       unsigned char*    data;
+       size_t    tlength;
+    CODE:
+       data    = NULL;
+       tlength = io_slurp(ig, &data);
+       RETVAL = newSVpv((char *)data,tlength);
+       myfree(data);
+    OUTPUT:
+       RETVAL
+
 MODULE = Imager                PACKAGE = Imager::IO    PREFIX = i_io_
 
-int
-i_io_write(ig, data_sv)
+IV
+i_io_raw_write(ig, data_sv)
        Imager::IO ig
        SV *data_sv
       PREINIT:
@@ -1138,12 +1197,12 @@ i_io_write(ig, data_sv)
        }
 #endif        
        data = SvPV(data_sv, size);
-        RETVAL = i_io_write(ig, data, size);
+        RETVAL = i_io_raw_write(ig, data, size);
       OUTPUT:
        RETVAL
 
 void
-i_io_read(ig, buffer_sv, size)
+i_io_raw_read(ig, buffer_sv, size)
        Imager::IO ig
        SV *buffer_sv
        IV size
@@ -1152,7 +1211,7 @@ i_io_read(ig, buffer_sv, size)
        ssize_t result;
       PPCODE:
         if (size <= 0)
-         croak("size negative in call to i_io_read()");
+         croak("size negative in call to i_io_raw_read()");
         /* prevent an undefined value warning if they supplied an 
           undef buffer.
            Orginally conditional on !SvOK(), but this will prevent the
@@ -1163,7 +1222,7 @@ i_io_read(ig, buffer_sv, size)
           sv_utf8_downgrade(buffer_sv, FALSE);
 #endif
        buffer = SvGROW(buffer_sv, size+1);
-        result = i_io_read(ig, buffer, size);
+        result = i_io_raw_read(ig, buffer, size);
         if (result >= 0) {
          SvCUR_set(buffer_sv, result);
          *SvEND(buffer_sv) = '\0';
@@ -1175,7 +1234,7 @@ i_io_read(ig, buffer_sv, size)
        SvSETMAGIC(ST(1));
 
 void
-i_io_read2(ig, size)
+i_io_raw_read2(ig, size)
        Imager::IO ig
        IV size
       PREINIT:
@@ -1187,7 +1246,7 @@ i_io_read2(ig, size)
          croak("size negative in call to i_io_read2()");
        buffer_sv = newSV(size);
        buffer = SvGROW(buffer_sv, size+1);
-        result = i_io_read(ig, buffer, size);
+        result = i_io_raw_read(ig, buffer, size);
         if (result >= 0) {
          SvCUR_set(buffer_sv, result);
          *SvEND(buffer_sv) = '\0';
@@ -1201,13 +1260,13 @@ i_io_read2(ig, size)
         }
 
 off_t
-i_io_seek(ig, position, whence)
+i_io_raw_seek(ig, position, whence)
        Imager::IO ig
        off_t position
        int whence
 
 int
-i_io_close(ig)
+i_io_raw_close(ig)
        Imager::IO ig
 
 void
@@ -1222,6 +1281,180 @@ i_io_CLONE_SKIP(...)
     OUTPUT:
        RETVAL
 
+int
+i_io_getc(ig)
+       Imager::IO ig
+
+int
+i_io_putc(ig, c)
+       Imager::IO ig
+        int c
+
+int
+i_io_close(ig)
+       Imager::IO ig
+
+int
+i_io_flush(ig)
+       Imager::IO ig
+
+int
+i_io_peekc(ig)
+       Imager::IO ig
+
+int
+i_io_seek(ig, off, whence)
+       Imager::IO ig
+       off_t off
+        int whence
+
+void
+i_io_peekn(ig, size)
+       Imager::IO ig
+       STRLEN size
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+       buffer_sv = newSV(size+1);
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_peekn(ig, buffer, size);
+        if (result >= 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(buffer_sv));
+       }
+       else {
+          /* discard it */
+         SvREFCNT_dec(buffer_sv);
+        }
+
+void
+i_io_read(ig, buffer_sv, size)
+       Imager::IO ig
+       SV *buffer_sv
+       IV size
+      PREINIT:
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size <= 0)
+         croak("size negative in call to i_io_read()");
+        /* prevent an undefined value warning if they supplied an 
+          undef buffer.
+           Orginally conditional on !SvOK(), but this will prevent the
+          downgrade from croaking */
+       sv_setpvn(buffer_sv, "", 0);
+#ifdef SvUTF8
+       if (SvUTF8(buffer_sv))
+          sv_utf8_downgrade(buffer_sv, FALSE);
+#endif
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_read(ig, buffer, size);
+        if (result >= 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(newSViv(result)));
+       }
+       ST(1) = buffer_sv;
+       SvSETMAGIC(ST(1));
+
+void
+i_io_read2(ig, size)
+       Imager::IO ig
+       STRLEN size
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size == 0)
+         croak("size zero in call to read2()");
+       buffer_sv = newSV(size);
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_read(ig, buffer, size);
+        if (result > 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(buffer_sv));
+       }
+       else {
+          /* discard it */
+         SvREFCNT_dec(buffer_sv);
+        }
+
+void
+i_io_gets(ig, size = 8192, eol = NEWLINE)
+       Imager::IO ig
+       STRLEN size
+       int eol
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size < 2)
+         croak("size too small in call to gets()");
+       buffer_sv = sv_2mortal(newSV(size+1));
+       buffer = SvPVX(buffer_sv);
+        result = i_io_gets(ig, buffer, size+1, eol);
+        if (result > 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(buffer_sv);
+       }
+
+IV
+i_io_write(ig, data_sv)
+       Imager::IO ig
+       SV *data_sv
+      PREINIT:
+        void *data;
+       STRLEN size;
+      CODE:
+#ifdef SvUTF8
+        if (SvUTF8(data_sv)) {
+         data_sv = sv_2mortal(newSVsv(data_sv));
+          /* yes, we want this to croak() if the SV can't be downgraded */
+         sv_utf8_downgrade(data_sv, FALSE);
+       }
+#endif        
+       data = SvPV(data_sv, size);
+        RETVAL = i_io_write(ig, data, size);
+      OUTPUT:
+       RETVAL
+
+void
+i_io_dump(ig, flags = I_IO_DUMP_DEFAULT)
+       Imager::IO ig
+       int flags
+
+bool
+i_io_set_buffered(ig, flag = 1)
+       Imager::IO ig
+       int flag
+
+bool
+i_io_is_buffered(ig)
+       Imager::IO ig
+
+bool
+i_io_eof(ig)
+       Imager::IO ig
+
+bool
+i_io_error(ig)
+       Imager::IO ig
+
 MODULE = Imager                PACKAGE = Imager
 
 PROTOTYPES: ENABLE
@@ -1238,22 +1471,6 @@ i_list_formats()
                      PUSHs(sv_2mortal(newSVpv(item,0)));
               }
 
-Imager::ImgRaw
-i_img_new()
-
-Imager::ImgRaw
-i_img_empty(im,x,y)
-    Imager::ImgRaw     im
-               i_img_dim     x
-              i_img_dim     y
-
-Imager::ImgRaw
-i_img_empty_ch(im,x,y,ch)
-    Imager::ImgRaw     im
-               i_img_dim     x
-              i_img_dim     y
-              int     ch
-
 Imager::ImgRaw
 i_sametype(im, x, y)
     Imager::ImgRaw im
@@ -1286,14 +1503,6 @@ i_log_entry(string,level)
 int
 i_log_enabled()
 
-void
-i_img_exorcise(im)
-    Imager::ImgRaw     im
-
-void
-i_img_destroy(im)
-    Imager::ImgRaw     im
-
 void
 i_img_info(im)
     Imager::ImgRaw     im
@@ -1888,6 +2097,10 @@ i_convert(src, avmain)
              if (len > inchan)
                inchan = len;
            }
+           else {
+             i_push_errorf(0, "invalid matrix: element %d is not an array ref", j);
+             XSRETURN(0);
+           }
           }
           coeff = mymalloc(sizeof(double) * outchan * inchan);
          for (j = 0; j < outchan; ++j) {
@@ -2110,7 +2323,7 @@ i_tt_has_chars(handle, text_sv, utf8)
         if (GIMME_V == G_ARRAY) {
           EXTEND(SP, count);
           for (i = 0; i < count; ++i) {
-            PUSHs(sv_2mortal(newSViv(work[i])));
+            PUSHs(boolSV(work[i]));
           }
         }
         else {
@@ -2312,7 +2525,7 @@ i_get_anonymous_color_histo(im, maxc = 0x40000000)
         XSRETURN(col_cnt);
 
 
-Imager::ImgRaw
+void
 i_transform(im,opx,opy,parm)
     Imager::ImgRaw     im
              PREINIT:
@@ -2325,7 +2538,8 @@ i_transform(im,opx,opy,parm)
              AV* av;
              SV* sv1;
              int i;
-             CODE:
+            i_img *result;
+             PPCODE:
              if (!SvROK(ST(1))) croak("Imager: Parameter 1 must be a reference to an array\n");
              if (!SvROK(ST(2))) croak("Imager: Parameter 2 must be a reference to an array\n");
              if (!SvROK(ST(3))) croak("Imager: Parameter 3 must be a reference to an array\n");
@@ -2353,15 +2567,18 @@ i_transform(im,opx,opy,parm)
                sv1=(*(av_fetch(av,i,0)));
                parm[i]=(double)SvNV(sv1);
              }
-             RETVAL=i_transform(im,opx,opxl,opy,opyl,parm,parmlen);
+             result=i_transform(im,opx,opxl,opy,opyl,parm,parmlen);
              myfree(parm);
              myfree(opy);
              myfree(opx);
-             ST(0) = sv_newmortal();
-             if (RETVAL == 0) ST(0)=&PL_sv_undef;
-             else sv_setref_pv(ST(0), "Imager::ImgRaw", (void*)RETVAL);
+            if (result) {
+              SV *result_sv = sv_newmortal();
+              EXTEND(SP, 1);
+              sv_setref_pv(result_sv, "Imager::ImgRaw", (void*)result);
+              PUSHs(result_sv);
+            }
 
-Imager::ImgRaw
+void
 i_transform2(sv_width,sv_height,channels,sv_ops,av_n_regs,av_c_regs,av_in_imgs)
        SV *sv_width
        SV *sv_height
@@ -2385,7 +2602,8 @@ i_transform2(sv_width,sv_height,channels,sv_ops,av_n_regs,av_c_regs,av_in_imgs)
              SV *sv1;
              IV tmp;
             int i;
-             CODE:
+            i_img *result;
+             PPCODE:
 
              in_imgs_count = av_len(av_in_imgs)+1;
             for (i = 0; i < in_imgs_count; ++i) {
@@ -2440,16 +2658,19 @@ i_transform2(sv_width,sv_height,channels,sv_ops,av_n_regs,av_c_regs,av_in_imgs)
              c_regs = mymalloc(c_regs_count * sizeof(i_color));
              /* I don't bother initializing the colou?r registers */
 
-            RETVAL=i_transform2(width, height, channels, ops, ops_count, 
+            result=i_transform2(width, height, channels, ops, ops_count, 
                                 n_regs, n_regs_count, 
                                 c_regs, c_regs_count, in_imgs, in_imgs_count);
             if (in_imgs)
                 myfree(in_imgs);
              myfree(n_regs);
             myfree(c_regs);
-             ST(0) = sv_newmortal();
-             if (RETVAL == 0) ST(0)=&PL_sv_undef;
-             else sv_setref_pv(ST(0), "Imager::ImgRaw", (void*)RETVAL);
+            if (result) {
+              SV *result_sv = sv_newmortal();
+              EXTEND(SP, 1);
+              sv_setref_pv(result_sv, "Imager::ImgRaw", (void*)result);
+              PUSHs(result_sv);
+            }
 
 
 void
@@ -2857,6 +3078,40 @@ Imager::ImgRaw
 i_img_to_rgb(src)
         Imager::ImgRaw src
 
+void
+i_img_make_palette(HV *quant_hv, ...)
+      PREINIT:
+        size_t count = items - 1;
+       i_quantize quant;
+       i_img **imgs = NULL;
+       ssize_t i;
+      PPCODE:
+        if (count <= 0)
+         croak("Please supply at least one image (%d)", (int)count);
+        imgs = mymalloc(sizeof(i_img *) * count);
+       for (i = 0; i < count; ++i) {
+         SV *img_sv = ST(i + 1);
+         if (SvROK(img_sv) && sv_derived_from(img_sv, "Imager::ImgRaw")) {
+           imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(img_sv)));
+         }
+         else {
+           myfree(imgs);
+           croak("Image %d is not an image object", (int)i+1);
+          }
+       }
+        memset(&quant, 0, sizeof(quant));
+       quant.version = 1;
+       quant.mc_size = 256;
+        ip_handle_quant_opts(aTHX_ &quant, quant_hv);
+       i_quant_makemap(&quant, imgs, count);
+       EXTEND(SP, quant.mc_count);
+       for (i = 0; i < quant.mc_count; ++i) {
+         SV *sv_c = make_i_color_sv(aTHX_ quant.mc_colors + i);
+         PUSHs(sv_c);
+       }
+       ip_cleanup_quant_opts(aTHX_ &quant);
+       
+
 void
 i_gpal(im, l, r, y)
         Imager::ImgRaw  im
@@ -3015,11 +3270,7 @@ i_getcolors(im, index, ...)
         colors = mymalloc(sizeof(i_color) * count);
         if (i_getcolors(im, index, colors, count)) {
           for (i = 0; i < count; ++i) {
-            i_color *pv;
-            SV *sv = sv_newmortal();
-            pv = mymalloc(sizeof(i_color));
-            *pv = colors[i];
-            sv_setref_pv(sv, "Imager::Color", (void *)pv);
+            SV *sv = make_i_color_sv(aTHX_ colors+i);
             PUSHs(sv);
           }
         }
@@ -3063,27 +3314,19 @@ i_img_virtual(im)
         Imager::ImgRaw  im
 
 void
-i_gsamp(im, l, r, y, ...)
+i_gsamp(im, l, r, y, channels)
         Imager::ImgRaw im
         i_img_dim l
         i_img_dim r
         i_img_dim y
+        i_channel_list channels
       PREINIT:
-        int *chans;
-        int chan_count;
         i_sample_t *data;
         i_img_dim count, i;
       PPCODE:
-        if (items < 5)
-          croak("No channel numbers supplied to g_samp()");
         if (l < r) {
-          chan_count = items - 4;
-          chans = mymalloc(sizeof(int) * chan_count);
-          for (i = 0; i < chan_count; ++i)
-            chans[i] = SvIV(ST(i+4));
-          data = mymalloc(sizeof(i_sample_t) * (r-l) * chan_count); /* XXX: memleak? */
-          count = i_gsamp(im, l, r, y, data, chans, chan_count);
-         myfree(chans);
+          data = mymalloc(sizeof(i_sample_t) * (r-l) * channels.count); /* XXX: memleak? */
+          count = i_gsamp(im, l, r, y, data, channels.channels, channels.count);
           if (GIMME_V == G_ARRAY) {
             EXTEND(SP, count);
             for (i = 0; i < count; ++i)
@@ -3103,7 +3346,7 @@ i_gsamp(im, l, r, y, ...)
         }
 
 undef_neg_int
-i_gsamp_bits(im, l, r, y, bits, target, offset, ...)
+i_gsamp_bits(im, l, r, y, bits, target, offset, channels)
         Imager::ImgRaw im
         i_img_dim l
         i_img_dim r
@@ -3111,9 +3354,8 @@ i_gsamp_bits(im, l, r, y, bits, target, offset, ...)
        int bits
        AV *target
        STRLEN offset
+        i_channel_list channels
       PREINIT:
-        int *chans;
-        int chan_count;
         unsigned *data;
         i_img_dim count, i;
       CODE:
@@ -3121,13 +3363,8 @@ i_gsamp_bits(im, l, r, y, bits, target, offset, ...)
         if (items < 8)
           croak("No channel numbers supplied to g_samp()");
         if (l < r) {
-          chan_count = items - 7;
-          chans = mymalloc(sizeof(int) * chan_count);
-          for (i = 0; i < chan_count; ++i)
-            chans[i] = SvIV(ST(i+7));
-          data = mymalloc(sizeof(unsigned) * (r-l) * chan_count);
-          count = i_gsamp_bits(im, l, r, y, data, chans, chan_count, bits);
-         myfree(chans);
+          data = mymalloc(sizeof(unsigned) * (r-l) * channels.count);
+          count = i_gsamp_bits(im, l, r, y, data, channels.channels, channels.count, bits);
          for (i = 0; i < count; ++i) {
            av_store(target, i+offset, newSVuv(data[i]));
          }
@@ -3141,70 +3378,116 @@ i_gsamp_bits(im, l, r, y, bits, target, offset, ...)
        RETVAL
 
 undef_neg_int
-i_psamp_bits(im, l, y, bits, channels_sv, data_av, data_offset = 0, pixel_count = -1)
+i_psamp_bits(im, l, y, bits, channels, data_av, data_offset = 0, pixel_count = -1)
         Imager::ImgRaw im
         i_img_dim l
         i_img_dim y
        int bits
-       SV *channels_sv
+       i_channel_list channels
        AV *data_av
-        int data_offset
-        int pixel_count
+        i_img_dim data_offset
+        i_img_dim pixel_count
       PREINIT:
-       int chan_count;
-       int *channels;
        STRLEN data_count;
        size_t data_used;
        unsigned *data;
        ptrdiff_t i;
       CODE:
        i_clear_error();
-       if (SvOK(channels_sv)) {
-         AV *channels_av;
-         if (!SvROK(channels_sv) || SvTYPE(SvRV(channels_sv)) != SVt_PVAV) {
-           croak("channels is not an array ref");
-         }
-         channels_av = (AV *)SvRV(channels_sv);
-         chan_count = av_len(channels_av) + 1;
-         if (chan_count < 1) {
-           croak("i_psamp_bits: no channels provided");
-         }
-         channels = mymalloc(sizeof(int) * chan_count);
-         for (i = 0; i < chan_count; ++i)
-           channels[i] = SvIV(*av_fetch(channels_av, i, 0));
-        }
-       else {
-         chan_count = im->channels;
-         channels = NULL;
-       }
 
        data_count = av_len(data_av) + 1;
        if (data_offset < 0) {
-         croak("data_offset must by non-negative");
+         croak("data_offset must be non-negative");
        }
        if (data_offset > data_count) {
          croak("data_offset greater than number of samples supplied");
         }
        if (pixel_count == -1 || 
-           data_offset + pixel_count * chan_count > data_count) {
-         pixel_count = (data_count - data_offset) / chan_count;
+           data_offset + pixel_count * channels.count > data_count) {
+         pixel_count = (data_count - data_offset) / channels.count;
        }
 
-       data_used = pixel_count * chan_count;
+       data_used = pixel_count * channels.count;
        data = mymalloc(sizeof(unsigned) * data_count);
        for (i = 0; i < data_used; ++i)
          data[i] = SvUV(*av_fetch(data_av, data_offset + i, 0));
 
-       RETVAL = i_psamp_bits(im, l, l + pixel_count, y, data, channels, 
-                             chan_count, bits);
+       RETVAL = i_psamp_bits(im, l, l + pixel_count, y, data, channels.channels
+                             channels.count, bits);
 
        if (data)
          myfree(data);
-       if (channels)
-         myfree(channels);
       OUTPUT:
        RETVAL
 
+undef_neg_int
+i_psamp(im, x, y, channels, data, offset = 0, width = -1)
+       Imager::ImgRaw im
+       i_img_dim x
+       i_img_dim y
+       i_channel_list channels
+        i_sample_list data
+       i_img_dim offset
+       i_img_dim width
+    PREINIT:
+       i_img_dim r;
+    CODE:
+       i_clear_error();
+       if (offset < 0) {
+         i_push_error(0, "offset must be non-negative");
+         XSRETURN_UNDEF;
+       }
+       if (offset > 0) {
+         if (offset > data.count) {
+           i_push_error(0, "offset greater than number of samples supplied");
+           XSRETURN_UNDEF;
+         }
+         data.samples += offset;
+         data.count -= offset;
+       }
+       if (width == -1 ||
+           width * channels.count > data.count) {
+         width = data.count / channels.count;
+        }
+       r = x + width;
+       RETVAL = i_psamp(im, x, r, y, data.samples, channels.channels, channels.count);
+    OUTPUT:
+       RETVAL
+
+undef_neg_int
+i_psampf(im, x, y, channels, data, offset = 0, width = -1)
+       Imager::ImgRaw im
+       i_img_dim x
+       i_img_dim y
+       i_channel_list channels
+        i_fsample_list data
+       i_img_dim offset
+       i_img_dim width
+    PREINIT:
+       i_img_dim r;
+    CODE:
+       i_clear_error();
+       if (offset < 0) {
+         i_push_error(0, "offset must be non-negative");
+         XSRETURN_UNDEF;
+       }
+       if (offset > 0) {
+         if (offset > data.count) {
+           i_push_error(0, "offset greater than number of samples supplied");
+           XSRETURN_UNDEF;
+         }
+         data.samples += offset;
+         data.count -= offset;
+       }
+       if (width == -1 ||
+           width * channels.count > data.count) {
+         width = data.count / channels.count;
+        }
+       r = x + width;
+       RETVAL = i_psampf(im, x, r, y, data.samples, channels.channels, channels.count);
+    OUTPUT:
+       RETVAL
+
 Imager::ImgRaw
 i_img_masked_new(targ, mask, x, y, w, h)
         Imager::ImgRaw targ
@@ -3280,27 +3563,19 @@ i_ppixf(im, x, y, cl)
         Imager::Color::Float cl
 
 void
-i_gsampf(im, l, r, y, ...)
+i_gsampf(im, l, r, y, channels)
         Imager::ImgRaw im
         i_img_dim l
         i_img_dim r
         i_img_dim y
+       i_channel_list channels
       PREINIT:
-        int *chans;
-        int chan_count;
         i_fsample_t *data;
         i_img_dim count, i;
       PPCODE:
-        if (items < 5)
-          croak("No channel numbers supplied to g_sampf()");
         if (l < r) {
-          chan_count = items - 4;
-          chans = mymalloc(sizeof(int) * chan_count);
-          for (i = 0; i < chan_count; ++i)
-            chans[i] = SvIV(ST(i+4));
-          data = mymalloc(sizeof(i_fsample_t) * (r-l) * chan_count);
-          count = i_gsampf(im, l, r, y, data, chans, chan_count);
-          myfree(chans);
+          data = mymalloc(sizeof(i_fsample_t) * (r-l) * channels.count);
+          count = i_gsampf(im, l, r, y, data, channels.channels, channels.count);
           if (GIMME_V == G_ARRAY) {
             EXTEND(SP, count);
             for (i = 0; i < count; ++i)
@@ -3401,11 +3676,7 @@ i_glin(im, l, r, y)
          if (GIMME_V == G_ARRAY) {
             EXTEND(SP, count);
             for (i = 0; i < count; ++i) {
-              SV *sv;
-              i_color *col = mymalloc(sizeof(i_color));
-              *col = vals[i];
-              sv = sv_newmortal();
-              sv_setref_pv(sv, "Imager::Color", (void *)col);
+              SV *sv = make_i_color_sv(aTHX_ vals+i);
               PUSHs(sv);
             }
           }
@@ -3452,6 +3723,12 @@ i_glinf(im, l, r, y)
           myfree(vals);
         }
 
+Imager::ImgRaw
+i_img_8_new(x, y, ch)
+        i_img_dim x
+        i_img_dim y
+        int ch
+
 Imager::ImgRaw
 i_img_16_new(x, y, ch)
         i_img_dim x
@@ -3778,6 +4055,34 @@ i_int_hlines_CLONE_SKIP(cls)
 
 #endif
 
+MODULE = Imager  PACKAGE = Imager::Context PREFIX=im_context_
+
+void
+im_context_DESTROY(ctx)
+   Imager::Context ctx
+
+#ifdef PERL_IMPLICIT_CONTEXT
+
+void
+im_context_CLONE(...)
+    CODE:
+      MY_CXT_CLONE;
+      (void)items;
+      /* the following sv_setref_pv() will free this inc */
+      im_context_refinc(MY_CXT.ctx, "CLONE");
+      MY_CXT.ctx = im_context_clone(MY_CXT.ctx, "CLONE");
+      sv_setref_pv(get_sv("Imager::_context", GV_ADD), "Imager::Context", MY_CXT.ctx);
+
+#endif
+
 BOOT:
         PERL_SET_GLOBAL_CALLBACKS;
-       PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
+       PERL_PL_SET_GLOBAL_CALLBACKS;
+#ifdef PERL_IMPLICIT_CONTEXT
+       {
+          MY_CXT_INIT;
+         (void)MY_CXT;
+       }
+#endif
+       start_context(aTHX);
+       im_get_context = perl_get_context;