]> git.imager.perl.org - imager.git/blobdiff - image.c
- correct documentation of default of raw image interleave read
[imager.git] / image.c
diff --git a/image.c b/image.c
index eba037c5d0c1a1bcb55e8bff032b1cc6da519a7d..5dba629f32ba0b8430775ce4698009de2cfe414c 100644 (file)
--- a/image.c
+++ b/image.c
@@ -1,5 +1,5 @@
-#include "image.h"
-#include "imagei.h"
+#include "imager.h"
+#include "imageri.h"
 
 /*
 =head1 NAME
@@ -39,18 +39,68 @@ Some of these functions are internal.
 /* Hack around an obscure linker bug on solaris - probably due to builtin gcc thingies */
 static void fake(void) { ceil(1); }
 
-static int i_ppix_d(i_img *im, int x, int y, i_color *val);
+static int i_ppix_d(i_img *im, int x, int y, const i_color *val);
 static int i_gpix_d(i_img *im, int x, int y, i_color *val);
 static int i_glin_d(i_img *im, int l, int r, int y, i_color *vals);
-static int i_plin_d(i_img *im, int l, int r, int y, i_color *vals);
-static int i_ppixf_d(i_img *im, int x, int y, i_fcolor *val);
+static int i_plin_d(i_img *im, int l, int r, int y, const i_color *vals);
+static int i_ppixf_d(i_img *im, int x, int y, const i_fcolor *val);
 static int i_gpixf_d(i_img *im, int x, int y, i_fcolor *val);
 static int i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals);
-static int i_plinf_d(i_img *im, int l, int r, int y, i_fcolor *vals);
+static int i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals);
 static int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, const int *chans, int chan_count);
 static int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, const int *chans, int chan_count);
-/*static int i_psamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count);
-  static int i_psampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count);*/
+
+/*
+=item i_img_alloc()
+=category Image Implementation
+
+Allocates a new i_img structure.
+
+When implementing a new image type perform the following steps in your
+image object creation function:
+
+=over
+
+=item 1.
+
+allocate the image with i_img_alloc().
+
+=item 2.
+
+initialize any function pointers or other data as needed, you can
+overwrite the whole block if you need to.
+
+=item 3.
+
+initialize Imager's internal data by calling i_img_init() on the image
+object.
+
+=back
+
+=cut
+*/
+
+i_img *
+i_img_alloc(void) {
+  return mymalloc(sizeof(i_img));
+}
+
+/*
+=item i_img_init(img)
+=category Image Implementation
+
+Imager interal initialization of images.
+
+Currently this does very little, in the future it may be used to
+support threads, or color profiles.
+
+=cut
+*/
+
+void
+i_img_init(i_img *img) {
+  img->im_data = NULL;
+}
 
 /* 
 =item ICL_new_internal(r, g, b, a)
@@ -71,7 +121,7 @@ ICL_new_internal(unsigned char r,unsigned char g,unsigned char b,unsigned char a
 
   mm_log((1,"ICL_new_internal(r %d,g %d,b %d,a %d)\n", r, g, b, a));
 
-  if ( (cl=mymalloc(sizeof(i_color))) == NULL) m_fatal(2,"malloc() error\n");
+  if ( (cl=mymalloc(sizeof(i_color))) == NULL) i_fatal(2,"malloc() error\n");
   cl->rgba.r = r;
   cl->rgba.g = g;
   cl->rgba.b = b;
@@ -100,7 +150,7 @@ ICL_set_internal(i_color *cl,unsigned char r,unsigned char g,unsigned char b,uns
   mm_log((1,"ICL_set_internal(cl* %p,r %d,g %d,b %d,a %d)\n",cl,r,g,b,a));
   if (cl == NULL)
     if ( (cl=mymalloc(sizeof(i_color))) == NULL)
-      m_fatal(2,"malloc() error\n");
+      i_fatal(2,"malloc() error\n");
   cl->rgba.r=r;
   cl->rgba.g=g;
   cl->rgba.b=b;
@@ -142,7 +192,7 @@ Dump color information to log - strictly for debugging.
 */
 
 void
-ICL_info(i_color *cl) {
+ICL_info(i_color const *cl) {
   mm_log((1,"i_color_info(cl* %p)\n",cl));
   mm_log((1,"i_color_info: (%d,%d,%d,%d)\n",cl->rgba.r,cl->rgba.g,cl->rgba.b,cl->rgba.a));
 }
@@ -173,7 +223,7 @@ i_fcolor *i_fcolor_new(double r, double g, double b, double a) {
 
   mm_log((1,"i_fcolor_new(r %g,g %g,b %g,a %g)\n", r, g, b, a));
 
-  if ( (cl=mymalloc(sizeof(i_fcolor))) == NULL) m_fatal(2,"malloc() error\n");
+  if ( (cl=mymalloc(sizeof(i_fcolor))) == NULL) i_fatal(2,"malloc() error\n");
   cl->rgba.r = r;
   cl->rgba.g = g;
   cl->rgba.b = b;
@@ -232,6 +282,9 @@ static i_img IIM_base_8bit_direct =
   NULL, /* i_f_setcolors */
 
   NULL, /* i_f_destroy */
+
+  i_gsamp_bits_fb,
+  NULL, /* i_f_psamp_bits */
 };
 
 /*static void set_8bit_direct(i_img *im) {
@@ -254,7 +307,14 @@ static i_img IIM_base_8bit_direct =
 /*
 =item IIM_new(x, y, ch)
 
-Creates a new image object I<x> pixels wide, and I<y> pixels high with I<ch> channels.
+=item i_img_8_new(x, y, ch)
+
+=category Image creation/destruction
+
+=synopsis i_img *img = i_img_8_new(width, height, channels);
+
+Creates a new image object I<x> pixels wide, and I<y> pixels high with
+I<ch> channels.
 
 =cut
 */
@@ -279,8 +339,6 @@ IIM_DESTROY(i_img *im) {
   /*   myfree(cl); */
 }
 
-
-
 /* 
 =item i_img_new()
 
@@ -296,8 +354,8 @@ i_img_new() {
   i_img *im;
   
   mm_log((1,"i_img_struct()\n"));
-  if ( (im=mymalloc(sizeof(i_img))) == NULL)
-    m_fatal(2,"malloc() error\n");
+
+  im = i_img_alloc();
   
   *im = IIM_base_8bit_direct;
   im->xsize=0;
@@ -306,6 +364,8 @@ i_img_new() {
   im->ch_mask=MAXINT;
   im->bytes=0;
   im->idata=NULL;
+
+  i_img_init(im);
   
   mm_log((1,"(%p) <- i_img_struct\n",im));
   return im;
@@ -368,8 +428,7 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   }
 
   if (im == NULL)
-    if ( (im=mymalloc(sizeof(i_img))) == NULL)
-      m_fatal(2,"malloc() error\n");
+    im = i_img_alloc();
 
   memcpy(im, &IIM_base_8bit_direct, sizeof(i_img));
   i_tags_new(&im->tags);
@@ -379,10 +438,12 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   im->ch_mask  = MAXINT;
   im->bytes=bytes;
   if ( (im->idata=mymalloc(im->bytes)) == NULL) 
-    m_fatal(2,"malloc() error\n"); 
+    i_fatal(2,"malloc() error\n"); 
   memset(im->idata,0,(size_t)im->bytes);
   
   im->ext_data = NULL;
+
+  i_img_init(im);
   
   mm_log((1,"(%p) <- i_img_empty_ch\n",im));
   return im;
@@ -418,11 +479,12 @@ i_img_exorcise(i_img *im) {
 }
 
 /* 
-=item i_img_destroy(im)
+=item i_img_destroy(img)
 
-Destroy image and free data via exorcise.
+=category Image creation/destruction
+=synopsis i_img_destroy(img)
 
-   im - Image pointer
+Destroy an image object
 
 =cut
 */
@@ -437,6 +499,8 @@ i_img_destroy(i_img *im) {
 /* 
 =item i_img_info(im, info)
 
+=category Image
+
 Return image information
 
    im - Image pointer
@@ -474,6 +538,9 @@ i_img_info(i_img *im,int *info) {
 /*
 =item i_img_setmask(im, ch_mask)
 
+=synopsis // only channel 0 writeable 
+=synopsis i_img_setmask(img, 0x01);
+
 Set the image channel mask for I<im> to I<ch_mask>.
 
 =cut
@@ -485,6 +552,8 @@ i_img_setmask(i_img *im,int ch_mask) { im->ch_mask=ch_mask; }
 /*
 =item i_img_getmask(im)
 
+=synopsis mask = i_img_getmask(img);
+
 Get the image channel mask for I<im>.
 
 =cut
@@ -495,6 +564,8 @@ i_img_getmask(i_img *im) { return im->ch_mask; }
 /*
 =item i_img_getchannels(im)
 
+=synopsis channels = i_img_getchannels(img);
+
 Get the number of channels in I<im>.
 
 =cut
@@ -502,11 +573,39 @@ Get the number of channels in I<im>.
 int
 i_img_getchannels(i_img *im) { return im->channels; }
 
+/*
+=item i_img_get_width(im)
+
+=synopsis width = i_img_get_width(im);
+
+Returns the width in pixels of the image.
+
+=cut
+*/
+i_img_dim
+i_img_get_width(i_img *im) {
+  return im->xsize;
+}
+
+/*
+=item i_img_get_height(im)
+
+=synopsis height = i_img_get_height(im);
 
+Returns the height in pixels of the image.
+
+=cut
+*/
+i_img_dim
+i_img_get_height(i_img *im) {
+  return im->ysize;
+}
 
 /*
 =item i_copyto_trans(im, src, x1, y1, x2, y2, tx, ty, trans)
 
+=category Image
+
 (x1,y1) (x2,y2) specifies the region to copy (in the source coordinates)
 (tx,ty) specifies the upper left corner for the target image.
 pass NULL in trans for non transparent i_colors.
@@ -515,7 +614,7 @@ pass NULL in trans for non transparent i_colors.
 */
 
 void
-i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_color *trans) {
+i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,const i_color *trans) {
   i_color pv;
   int x,y,t,ttx,tty,tt,ch;
 
@@ -545,75 +644,34 @@ i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_
 }
 
 /*
-=item i_copyto(dest, src, x1, y1, x2, y2, tx, ty)
+=item i_copy(src)
 
-Copies image data from the area (x1,y1)-[x2,y2] in the source image to
-a rectangle the same size with it's top-left corner at (tx,ty) in the
-destination image.
+=category Image
 
-If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped.
-
-=cut
-*/
-
-void
-i_copyto(i_img *im, i_img *src, int x1, int y1, int x2, int y2, int tx, int ty) {
-  int x, y, t, ttx, tty;
-  
-  if (x2<x1) { t=x1; x1=x2; x2=t; }
-  if (y2<y1) { t=y1; y1=y2; y2=t; }
-  
-  mm_log((1,"i_copyto(im* %p, src %p, x1 %d, y1 %d, x2 %d, y2 %d, tx %d, ty %d)\n",
-         im, src, x1, y1, x2, y2, tx, ty));
-  
-  if (im->bits == i_8_bits) {
-    i_color pv;
-    tty = ty;
-    for(y=y1; y<y2; y++) {
-      ttx = tx;
-      for(x=x1; x<x2; x++) {
-        i_gpix(src, x,   y,   &pv);
-        i_ppix(im,  ttx, tty, &pv);
-        ttx++;
-      }
-      tty++;
-    }
-  }
-  else {
-    i_fcolor pv;
-    tty = ty;
-    for(y=y1; y<y2; y++) {
-      ttx = tx;
-      for(x=x1; x<x2; x++) {
-        i_gpixf(src, x,   y,   &pv);
-        i_ppixf(im,  ttx, tty, &pv);
-        ttx++;
-      }
-      tty++;
-    }
-  }
-}
+Creates a new image that is a copy of src.
 
-/*
-=item i_copy(im, src)
+Tags are not copied, only the image data.
 
-Copies the contents of the image I<src> over the image I<im>.
+Returns: i_img *
 
 =cut
 */
 
-void
-i_copy(i_img *im, i_img *src) {
+i_img *
+i_copy(i_img *src) {
   int y, y1, x1;
+  i_img *im = i_sametype(src, src->xsize, src->ysize);
 
-  mm_log((1,"i_copy(im* %p,src %p)\n", im, src));
+  mm_log((1,"i_copy(src %p)\n", src));
+
+  if (!im)
+    return NULL;
 
   x1 = src->xsize;
   y1 = src->ysize;
   if (src->type == i_direct_type) {
     if (src->bits == i_8_bits) {
       i_color *pv;
-      i_img_empty_ch(im, x1, y1, src->channels);
       pv = mymalloc(sizeof(i_color) * x1);
       
       for (y = 0; y < y1; ++y) {
@@ -624,14 +682,6 @@ i_copy(i_img *im, i_img *src) {
     }
     else {
       i_fcolor *pv;
-      if (src->bits == i_16_bits)
-       i_img_16_new_low(im, x1, y1, src->channels);
-      else if (src->bits == i_double_bits)
-       i_img_double_new_low(im, x1, y1, src->channels);
-      else {
-       fprintf(stderr, "i_copy(): Unknown image bit size %d\n", src->bits);
-       return; /* I dunno */
-      }
 
       pv = mymalloc(sizeof(i_fcolor) * x1);
       for (y = 0; y < y1; ++y) {
@@ -642,20 +692,8 @@ i_copy(i_img *im, i_img *src) {
     }
   }
   else {
-    i_color temp;
-    int index;
-    int count;
     i_palidx *vals;
 
-    /* paletted image */
-    i_img_pal_new_low(im, x1, y1, src->channels, i_maxcolors(src));
-    /* copy across the palette */
-    count = i_colorcount(src);
-    for (index = 0; index < count; ++index) {
-      i_getcolors(src, index, &temp, 1);
-      i_addcolors(im, &temp, 1);
-    }
-
     vals = mymalloc(sizeof(i_palidx) * x1);
     for (y = 0; y < y1; ++y) {
       i_gpal(src, 0, x1, y, vals);
@@ -663,102 +701,8 @@ i_copy(i_img *im, i_img *src) {
     }
     myfree(vals);
   }
-}
-
 
-/*
-=item i_rubthru(im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy )
-
-Takes the sub image I<src[src_minx, src_maxx)[src_miny, src_maxy)> and
-overlays it at (I<tx>,I<ty>) on the image object.
-
-The alpha channel of each pixel in I<src> is used to control how much
-the existing colour in I<im> is replaced, if it is 255 then the colour
-is completely replaced, if it is 0 then the original colour is left 
-unmodified.
-
-=cut
-*/
-
-int
-i_rubthru(i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny,
-         int src_maxx, int src_maxy) {
-  int x, y, ttx, tty;
-  int chancount;
-  int chans[3];
-  int alphachan;
-  int ch;
-
-  mm_log((1,"i_rubthru(im %p, src %p, tx %d, ty %d, src_minx %d, "
-         "src_miny %d, src_maxx %d, src_maxy %d)\n",
-         im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy));
-  i_clear_error();
-
-  if (im->channels == 3 && src->channels == 4) {
-    chancount = 3;
-    chans[0] = 0; chans[1] = 1; chans[2] = 2;
-    alphachan = 3;
-  }
-  else if (im->channels == 3 && src->channels == 2) {
-    chancount = 3;
-    chans[0] = chans[1] = chans[2] = 0;
-    alphachan = 1;
-  }
-  else if (im->channels == 1 && src->channels == 2) {
-    chancount = 1;
-    chans[0] = 0;
-    alphachan = 1;
-  }
-  else {
-    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (3,2) or (1,2)");
-    return 0;
-  }
-
-  if (im->bits <= 8) {
-    /* if you change this code, please make sure the else branch is
-       changed in a similar fashion - TC */
-    int alpha;
-    i_color pv, orig, dest;
-    tty = ty;
-    for(y = src_miny; y < src_maxy; y++) {
-      ttx = tx;
-      for(x = src_minx; x < src_maxx; x++) {
-        i_gpix(src, x,   y,   &pv);
-        i_gpix(im,  ttx, tty, &orig);
-        alpha = pv.channel[alphachan];
-        for (ch = 0; ch < chancount; ++ch) {
-          dest.channel[ch] = (alpha * pv.channel[chans[ch]]
-                              + (255 - alpha) * orig.channel[ch])/255;
-        }
-        i_ppix(im, ttx, tty, &dest);
-       ttx++;
-      }
-      tty++;
-    }
-  }
-  else {
-    double alpha;
-    i_fcolor pv, orig, dest;
-
-    tty = ty;
-    for(y = src_miny; y < src_maxy; y++) {
-      ttx = tx;
-      for(x = src_minx; x < src_maxx; x++) {
-        i_gpixf(src, x,   y,   &pv);
-        i_gpixf(im,  ttx, tty, &orig);
-        alpha = pv.channel[alphachan];
-        for (ch = 0; ch < chancount; ++ch) {
-          dest.channel[ch] = alpha * pv.channel[chans[ch]]
-           + (1 - alpha) * orig.channel[ch];
-        }
-        i_ppixf(im, ttx, tty, &dest);
-        ttx++;
-      }
-      tty++;
-    }
-  }
-
-  return 1;
+  return im;
 }
 
 
@@ -909,6 +853,7 @@ i_scaleaxis(i_img *im, float Value, int Axis) {
   i_color val,val1,val2;
   i_img *new_img;
 
+  i_clear_error();
   mm_log((1,"i_scaleaxis(im %p,Value %.2f,Axis %d)\n",im,Value,Axis));
 
 
@@ -936,6 +881,10 @@ i_scaleaxis(i_img *im, float Value, int Axis) {
   }
   
   new_img = i_img_empty_ch(NULL, hsize, vsize, im->channels);
+  if (!new_img) {
+    i_push_error(0, "cannot create output image");
+    return NULL;
+  }
   
   /* 1.4 is a magic number, setting it to 2 will cause rather blurred images */
   LanczosWidthFactor = (Value >= 1) ? 1 : (int) (1.4/Value); 
@@ -1073,6 +1022,9 @@ i_scale_nn(i_img *im, float scx, float scy) {
 /*
 =item i_sametype(i_img *im, int xsize, int ysize)
 
+=category Image creation/destruction
+=synopsis i_img *img = i_sametype(src, width, height);
+
 Returns an image of the same type (sample size, channels, paletted/direct).
 
 For paletted images the palette is copied from the source.
@@ -1113,6 +1065,9 @@ i_img *i_sametype(i_img *src, int xsize, int ysize) {
 /*
 =item i_sametype_chans(i_img *im, int xsize, int ysize, int channels)
 
+=category Image creation/destruction
+=synopsis i_img *img = i_sametype_chans(src, width, height, channels);
+
 Returns an image of the same type (sample size).
 
 For paletted images the equivalent direct type is returned.
@@ -1282,26 +1237,139 @@ to indicate that it was more than max colors
 
 =cut
 */
+/* This function has been changed and is now faster. It's using
+ * i_gsamp instead of i_gpix */
 int
 i_count_colors(i_img *im,int maxc) {
   struct octt *ct;
   int x,y;
-  int xsize,ysize;
-  i_color val;
   int colorcnt;
+  int channels[3];
+  int *samp_chans;
+  i_sample_t * samp;
+  int xsize = im->xsize; 
+  int ysize = im->ysize;
+  int samp_cnt = 3 * xsize;
+
+  if (im->channels >= 3) {
+    samp_chans = NULL;
+  }
+  else {
+    channels[0] = channels[1] = channels[2] = 0;
+    samp_chans = channels;
+  }
+
+  ct = octt_new();
 
-  mm_log((1,"i_count_colors(im 0x%08X,maxc %d)\n"));
+  samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t));
 
-  xsize=im->xsize; 
-  ysize=im->ysize;
-  ct=octt_new();
-  colorcnt=0;
-  for(y=0;y<ysize;y++) for(x=0;x<xsize;x++) {
-    i_gpix(im,x,y,&val);
-    colorcnt+=octt_add(ct,val.rgb.r,val.rgb.g,val.rgb.b);
-    if (colorcnt > maxc) { octt_delete(ct); return -1; }
+  colorcnt = 0;
+  for(y = 0; y < ysize; ) {
+      i_gsamp(im, 0, xsize, y++, samp, samp_chans, 3);
+      for(x = 0; x < samp_cnt; ) {
+          colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]);
+          x += 3;
+          if (colorcnt > maxc) { 
+              octt_delete(ct); 
+              return -1; 
+          }
+      }
   }
+  myfree(samp);
+  octt_delete(ct);
+  return colorcnt;
+}
+
+/* sorts the array ra[0..n-1] into increasing order using heapsort algorithm 
+ * (adapted from the Numerical Recipes)
+ */
+/* Needed by get_anonymous_color_histo */
+static void
+hpsort(unsigned int n, unsigned *ra) {
+    unsigned int i,
+                 ir,
+                 j,
+                 l, 
+                 rra;
+
+    if (n < 2) return;
+    l = n >> 1;
+    ir = n - 1;
+    for(;;) {
+        if (l > 0) {
+            rra = ra[--l];
+        }
+        else {
+            rra = ra[ir];
+            ra[ir] = ra[0];
+            if (--ir == 0) {
+                ra[0] = rra;
+                break;
+            }
+        }
+        i = l;
+        j = 2 * l + 1;
+        while (j <= ir) {
+            if (j < ir && ra[j] < ra[j+1]) j++;
+            if (rra < ra[j]) {
+                ra[i] = ra[j];
+                i = j;
+                j++; j <<= 1; j--;
+            }
+            else break;
+        }
+        ra[i] = rra;
+    }
+}
+
+/* This function constructs an ordered list which represents how much the
+ * different colors are used. So for instance (100, 100, 500) means that one
+ * color is used for 500 pixels, another for 100 pixels and another for 100
+ * pixels. It's tuned for performance. You might not like the way I've hardcoded
+ * the maxc ;-) and you might want to change the name... */
+/* Uses octt_histo */
+int
+i_get_anonymous_color_histo(i_img *im, unsigned int **col_usage, int maxc) {
+  struct octt *ct;
+  int x,y;
+  int colorcnt;
+  unsigned int *col_usage_it;
+  i_sample_t * samp;
+  int channels[3];
+  int *samp_chans;
+  
+  int xsize = im->xsize; 
+  int ysize = im->ysize;
+  int samp_cnt = 3 * xsize;
+  ct = octt_new();
+  
+  samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t));
+  
+  if (im->channels >= 3) {
+    samp_chans = NULL;
+  }
+  else {
+    channels[0] = channels[1] = channels[2] = 0;
+    samp_chans = channels;
+  }
+
+  colorcnt = 0;
+  for(y = 0; y < ysize; ) {
+    i_gsamp(im, 0, xsize, y++, samp, samp_chans, 3);
+    for(x = 0; x < samp_cnt; ) {
+      colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]);
+      x += 3;
+      if (colorcnt > maxc) { 
+       octt_delete(ct); 
+       return -1; 
+      }
+    }
+  }
+  myfree(samp);
+  /* Now that we know the number of colours... */
+  col_usage_it = *col_usage = (unsigned int *) mymalloc(colorcnt * sizeof(unsigned int));
+  octt_histo(ct, &col_usage_it);
+  hpsort(colorcnt, *col_usage);
   octt_delete(ct);
   return colorcnt;
 }
@@ -1328,7 +1396,7 @@ Returns 0 if the pixel could be set, -1 otherwise.
 */
 static
 int
-i_ppix_d(i_img *im, int x, int y, i_color *val) {
+i_ppix_d(i_img *im, int x, int y, const i_color *val) {
   int ch;
   
   if ( x>-1 && x<im->xsize && y>-1 && y<im->ysize ) {
@@ -1420,7 +1488,7 @@ Returns the number of pixels copied (eg. if r, l or y is out of range)
 */
 static
 int
-i_plin_d(i_img *im, int l, int r, int y, i_color *vals) {
+i_plin_d(i_img *im, int l, int r, int y, const i_color *vals) {
   int ch, count, i;
   unsigned char *data;
   if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
@@ -1449,7 +1517,7 @@ i_plin_d(i_img *im, int l, int r, int y, i_color *vals) {
 */
 static
 int
-i_ppixf_d(i_img *im, int x, int y, i_fcolor *val) {
+i_ppixf_d(i_img *im, int x, int y, const i_fcolor *val) {
   int ch;
   
   if ( x>-1 && x<im->xsize && y>-1 && y<im->ysize ) {
@@ -1537,7 +1605,7 @@ Returns the number of pixels copied (eg. if r, l or y is out of range)
 */
 static
 int
-i_plinf_d(i_img *im, int l, int r, int y, i_fcolor *vals) {
+i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals) {
   int ch, count, i;
   unsigned char *data;
   if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
@@ -1601,6 +1669,11 @@ i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps,
       }
     }
     else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_errorf(0, "chan_count %d out of range, must be >0, <= channels", 
+                     chan_count);
+       return 0;
+      }
       for (i = 0; i < w; ++i) {
         for (ch = 0; ch < chan_count; ++ch) {
           *samps++ = data[ch];
@@ -1663,6 +1736,11 @@ i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps,
       }
     }
     else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_errorf(0, "chan_count %d out of range, must be >0, <= channels", 
+                     chan_count);
+       return 0;
+      }
       for (i = 0; i < w; ++i) {
         for (ch = 0; ch < chan_count; ++ch) {
           *samps++ = Sample8ToF(data[ch]);
@@ -1693,7 +1771,7 @@ i_sample_t versions.
 =cut
 */
 
-int i_ppixf_fp(i_img *im, int x, int y, i_fcolor *pix) {
+int i_ppixf_fp(i_img *im, int x, int y, const i_fcolor *pix) {
   i_color temp;
   int ch;
 
@@ -1726,7 +1804,7 @@ int i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix) {
 
 =cut
 */
-int i_plinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) {
+int i_plinf_fp(i_img *im, int l, int r, int y, const i_fcolor *pix) {
   i_color *work;
 
   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
@@ -1831,11 +1909,11 @@ im->ext_data points at.
 
 =over
 
-=item i_addcolors_forward(i_img *im, i_color *colors, int count)
+=item i_addcolors_forward(i_img *im, const i_color *colors, int count)
 
 =cut
 */
-int i_addcolors_forward(i_img *im, i_color *colors, int count) {
+int i_addcolors_forward(i_img *im, const i_color *colors, int count) {
   return i_addcolors(*(i_img **)im->ext_data, colors, count);
 }
 
@@ -1849,11 +1927,11 @@ int i_getcolors_forward(i_img *im, int i, i_color *color, int count) {
 }
 
 /*
-=item i_setcolors_forward(i_img *im, int i, i_color *color, int count)
+=item i_setcolors_forward(i_img *im, int i, const i_color *color, int count)
 
 =cut
 */
-int i_setcolors_forward(i_img *im, int i, i_color *color, int count) {
+int i_setcolors_forward(i_img *im, int i, const i_color *color, int count) {
   return i_setcolors(*(i_img **)im->ext_data, i, color, count);
 }
 
@@ -1876,17 +1954,91 @@ int i_maxcolors_forward(i_img *im) {
 }
 
 /*
-=item i_findcolor_forward(i_img *im, i_color *color, i_palidx *entry)
+=item i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry)
 
 =cut
 */
-int i_findcolor_forward(i_img *im, i_color *color, i_palidx *entry) {
+int i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry) {
   return i_findcolor(*(i_img **)im->ext_data, color, entry);
 }
 
 /*
 =back
 
+=head2 Fallback handler
+
+=over
+
+=item i_gsamp_bits_fb
+
+=cut
+*/
+
+int 
+i_gsamp_bits_fb(i_img *im, int l, int r, int y, unsigned *samps, 
+               const int *chans, int chan_count, int bits) {
+  if (bits < 1 || bits > 32) {
+    i_push_error(0, "Invalid bits, must be 1..32");
+    return -1;
+  }
+
+  if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) {
+    double scale;
+    int ch, count, i, w;
+    
+    if (bits == 32)
+      scale = 4294967295.0;
+    else
+      scale = (double)(1 << bits) - 1;
+
+    if (r > im->xsize)
+      r = im->xsize;
+    w = r - l;
+    count = 0;
+
+    if (chans) {
+      /* make sure we have good channel numbers */
+      for (ch = 0; ch < chan_count; ++ch) {
+        if (chans[ch] < 0 || chans[ch] >= im->channels) {
+          i_push_errorf(0, "No channel %d in this image", chans[ch]);
+          return -1;
+        }
+      }
+      for (i = 0; i < w; ++i) {
+       i_fcolor c;
+       i_gpixf(im, l+i, y, &c);
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = (unsigned)(c.channel[ch] * scale + 0.5);
+          ++count;
+        }
+      }
+    }
+    else {
+      if (chan_count <= 0 || chan_count > im->channels) {
+       i_push_error(0, "Invalid channel count");
+       return -1;
+      }
+      for (i = 0; i < w; ++i) {
+       i_fcolor c;
+       i_gpixf(im, l+i, y, &c);
+        for (ch = 0; ch < chan_count; ++ch) {
+          *samps++ = (unsigned)(c.channel[ch] * scale + 0.5);
+          ++count;
+        }
+      }
+    }
+
+    return count;
+  }
+  else {
+    i_push_error(0, "Image position outside of image");
+    return -1;
+  }
+}
+
+/*
+=back
+
 =head2 Stream reading and writing wrapper functions
 
 =over
@@ -2072,7 +2224,37 @@ int i_free_gen_write_data(i_gen_write_data *info, int flush)
   return result;
 }
 
+struct magic_entry {
+  unsigned char *magic;
+  size_t magic_size;
+  char *name;
+  unsigned char *mask;  
+};
 
+static int
+test_magic(unsigned char *buffer, size_t length, struct magic_entry const *magic) {
+  if (length < magic->magic_size)
+    return 0;
+  if (magic->mask) {
+    int i;
+    unsigned char *bufp = buffer, 
+      *maskp = magic->mask, 
+      *magicp = magic->magic;
+
+    for (i = 0; i < magic->magic_size; ++i) {
+      int mask = *maskp == 'x' ? 0xFF : *maskp == ' ' ? 0 : *maskp;
+      ++maskp;
+
+      if ((*bufp++ & mask) != (*magicp++ & mask)) 
+       return 0;
+    }
+
+    return 1;
+  }
+  else {
+    return !memcmp(magic->magic, buffer, magic->magic_size);
+  }
+}
 
 /*
 =item i_test_format_probe(io_glue *data, int length)
@@ -2082,31 +2264,80 @@ Check the beginning of the supplied file for a 'magic number'
 =cut
 */
 
+#define FORMAT_ENTRY(magic, type) \
+  { (unsigned char *)(magic ""), sizeof(magic)-1, type }
+#define FORMAT_ENTRY2(magic, type, mask) \
+  { (unsigned char *)(magic ""), sizeof(magic)-1, type, (unsigned char *)(mask) }
 
-char *
+const char *
 i_test_format_probe(io_glue *data, int length) {
+  static const struct magic_entry formats[] = {
+    FORMAT_ENTRY("\xFF\xD8", "jpeg"),
+    FORMAT_ENTRY("GIF87a", "gif"),
+    FORMAT_ENTRY("GIF89a", "gif"),
+    FORMAT_ENTRY("MM\0*", "tiff"),
+    FORMAT_ENTRY("II*\0", "tiff"),
+    FORMAT_ENTRY("BM", "bmp"),
+    FORMAT_ENTRY("\x89PNG\x0d\x0a\x1a\x0a", "png"),
+    FORMAT_ENTRY("P1", "pnm"),
+    FORMAT_ENTRY("P2", "pnm"),
+    FORMAT_ENTRY("P3", "pnm"),
+    FORMAT_ENTRY("P4", "pnm"),
+    FORMAT_ENTRY("P5", "pnm"),
+    FORMAT_ENTRY("P6", "pnm"),
+    FORMAT_ENTRY("/* XPM", "xpm"),
+    FORMAT_ENTRY("\x8aMNG", "mng"),
+    FORMAT_ENTRY("\x8aJNG", "jng"),
+    /* SGI RGB - with various possible parameters to avoid false positives
+       on similar files 
+       values are: 2 byte magic, rle flags (0 or 1), bytes/sample (1 or 2)
+    */
+    FORMAT_ENTRY("\x01\xDA\x00\x01", "sgi"),
+    FORMAT_ENTRY("\x01\xDA\x00\x02", "sgi"),
+    FORMAT_ENTRY("\x01\xDA\x01\x01", "sgi"),
+    FORMAT_ENTRY("\x01\xDA\x01\x02", "sgi"),
+    
+    FORMAT_ENTRY2("FORM    ILBM", "ilbm", "xxxx    xxxx"),
+
+    /* different versions of PCX format 
+       http://www.fileformat.info/format/pcx/
+    */
+    FORMAT_ENTRY("\x0A\x00\x01", "pcx"),
+    FORMAT_ENTRY("\x0A\x02\x01", "pcx"),
+    FORMAT_ENTRY("\x0A\x03\x01", "pcx"),
+    FORMAT_ENTRY("\x0A\x04\x01", "pcx"),
+    FORMAT_ENTRY("\x0A\x05\x01", "pcx"),
+
+    /* FITS - http://fits.gsfc.nasa.gov/ */
+    FORMAT_ENTRY("SIMPLE  =", "fits"),
+
+    /* PSD - Photoshop */
+    FORMAT_ENTRY("8BPS\x00\x01", "psd"),
+    
+    /* EPS - Encapsulated Postscript */
+    /* only reading 18 chars, so we don't include the F in EPSF */
+    FORMAT_ENTRY("%!PS-Adobe-2.0 EPS", "eps"),
 
-  static struct {
-    char *magic;
-    char *name;
-  } formats[] = {
-    {"\xFF\xD8", "jpeg"},
-    {"GIF87a", "gif"},
-    {"GIF89a", "gif"},
-    {"MM\0*", "tiff"},
-    {"II*\0", "tiff"},
-    {"BM", "bmp"},
-    {"\x89PNG\x0d\x0a\x1a\x0a", "png"},
-    {"P1", "pnm"},
-    {"P2", "pnm"},
-    {"P3", "pnm"},
-    {"P4", "pnm"},
-    {"P5", "pnm"},
-    {"P6", "pnm"},
+    /* Utah RLE */
+    FORMAT_ENTRY("\x52\xCC", "utah"),
+
+    /* GZIP compressed, only matching deflate for now */
+    FORMAT_ENTRY("\x1F\x8B\x08", "gzip"),
+
+    /* bzip2 compressed */
+    FORMAT_ENTRY("BZh", "bzip2"),
   };
+  static const struct magic_entry more_formats[] = {
+    /* these were originally both listed as ico, but cur files can
+       include hotspot information */
+    FORMAT_ENTRY("\x00\x00\x01\x00", "ico"), /* Windows icon */
+    FORMAT_ENTRY("\x00\x00\x02\x00", "cur"), /* Windows cursor */
+    FORMAT_ENTRY2("\x00\x00\x00\x00\x00\x00\x00\x07", 
+                 "xwd", "    xxxx"), /* X Windows Dump */
+  };
+
   unsigned int i;
-  char head[18];
-  char *match = NULL;
+  unsigned char head[18];
   ssize_t rc;
 
   io_glue_commit_types(data);
@@ -2115,36 +2346,141 @@ i_test_format_probe(io_glue *data, int length) {
   data->seekcb(data, -rc, SEEK_CUR);
 
   for(i=0; i<sizeof(formats)/sizeof(formats[0]); i++) { 
-    int c;
-    ssize_t len = strlen(formats[i].magic);
-    if (rc<len) continue;
-    c = !strncmp(formats[i].magic, head, len);
-    if (c) {
-      match = formats[i].name;
-      break;
-    }
+    struct magic_entry const *entry = formats + i;
+
+    if (test_magic(head, rc, entry)) 
+      return entry->name;
   }
 
-  /*
-    if (match && !strcmp(match, "jpeg")) {
-    unsigned int x0, x1;
-    rc = data->readcb(data, head, 18);
-    if (rc == -1) return NULL;
-    x0 = (unsigned char)head[0];
-    x1 = (unsigned char)head[1];
-    data->seekcb(data, -rc, SEEK_CUR);
-    printf("Jpeg reread: %x %x\n", x0, x1);
+  if ((rc == 18) &&
+      tga_header_verify(head))
+    return "tga";
+
+  for(i=0; i<sizeof(more_formats)/sizeof(more_formats[0]); i++) { 
+    struct magic_entry const *entry = more_formats + i;
+
+    if (test_magic(head, rc, entry)) 
+      return entry->name;
+  }
+
+  return NULL;
+}
+
+/*
+=item i_img_is_monochrome(img, &zero_is_white)
+
+Tests an image to check it meets our monochrome tests.
+
+The idea is that a file writer can use this to test where it should
+write the image in whatever bi-level format it uses, eg. pbm for pnm.
+
+For performance of encoders we require monochrome images:
+
+=over
+
+=item *
+
+be paletted
+
+=item *
+
+have a palette of two colors, containing only (0,0,0) and
+(255,255,255) in either order.
+
+=back
+
+zero_is_white is set to non-zero iff the first palette entry is white.
+
+=cut
+*/
+
+int
+i_img_is_monochrome(i_img *im, int *zero_is_white) {
+  if (im->type == i_palette_type
+      && i_colorcount(im) == 2) {
+    i_color colors[2];
+    i_getcolors(im, 0, colors, 2);
+    if (im->channels == 3) {
+      if (colors[0].rgb.r == 255 && 
+          colors[0].rgb.g == 255 &&
+          colors[0].rgb.b == 255 &&
+          colors[1].rgb.r == 0 &&
+          colors[1].rgb.g == 0 &&
+          colors[1].rgb.b == 0) {
+        *zero_is_white = 1;
+        return 1;
+      }
+      else if (colors[0].rgb.r == 0 && 
+               colors[0].rgb.g == 0 &&
+               colors[0].rgb.b == 0 &&
+               colors[1].rgb.r == 255 &&
+               colors[1].rgb.g == 255 &&
+               colors[1].rgb.b == 255) {
+        *zero_is_white = 0;
+        return 1;
+      }
+    }
+    else if (im->channels == 1) {
+      if (colors[0].channel[0] == 255 &&
+          colors[1].channel[0] == 0) {
+        *zero_is_white = 1;
+        return 1;
+      }
+      else if (colors[0].channel[0] == 0 &&
+               colors[1].channel[0] == 255) {
+        *zero_is_white = 0;
+        return 1;         
+      }
     }
-  */
+  }
+
+  *zero_is_white = 0;
+  return 0;
+}
 
-  if (!match && 
-      (rc == 18) &&
-      tga_header_verify(head)) return "tga";
-  return match;
+/*
+=item i_get_file_background(im, &bg)
+
+Retrieve the file write background color tag from the image.
+
+If not present, returns black.
+
+=cut
+*/
+
+void
+i_get_file_background(i_img *im, i_color *bg) {
+  if (!i_tags_get_color(&im->tags, "i_background", 0, bg)) {
+    /* black default */
+    bg->channel[0] = bg->channel[1] = bg->channel[2] = 0;
+  }
+  /* always full alpha */
+  bg->channel[3] = 255;
 }
 
+/*
+=item i_get_file_backgroundf(im, &bg)
+
+Retrieve the file write background color tag from the image as a
+floating point color.
+
+Implemented in terms of i_get_file_background().
+
+If not present, returns black.
 
+=cut
+*/
 
+void
+i_get_file_backgroundf(i_img *im, i_fcolor *fbg) {
+  i_color bg;
+
+  i_get_file_background(im, &bg);
+  fbg->rgba.r = Sample8ToF(bg.rgba.r);
+  fbg->rgba.g = Sample8ToF(bg.rgba.g);
+  fbg->rgba.b = Sample8ToF(bg.rgba.b);
+  fbg->rgba.a = 1.0;
+}
 
 /*
 =back