diff --git a/src/modules/image/Makefile.src b/src/modules/image/Makefile.src
index fe921440b024437f8aebff480b40c0aa9abab1eb..d8c5f3ca9e5c09e43cec2c78035a13b7e38e555f 100644
--- a/src/modules/image/Makefile.src
+++ b/src/modules/image/Makefile.src
@@ -3,7 +3,7 @@ VPATH=@srcdir@:@srcdir@/../..:../..
 PREFLAGS=$(DEFINES) -I$(SRCDIR) -I$(SRCDIR)/../.. -I../..
 CFLAGS=$(PREFLAGS) $(OTHERFLAGS) @DEFS@
 
-FILES=image.o font.o quant.o lzw.o togif.o
+FILES=image.o font.o quant.o lzw.o togif.o matrix.o pnm.o blit.o
 
 image.a: $(FILES)
 	-rm -f image.a
diff --git a/src/modules/image/blit.c b/src/modules/image/blit.c
new file mode 100644
index 0000000000000000000000000000000000000000..fbff9cf4cc2c7fd57a1332fe242cca999e0ef3e8
--- /dev/null
+++ b/src/modules/image/blit.c
@@ -0,0 +1,358 @@
+#include "global.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "stralloc.h"
+#include "global.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "array.h"
+#include "error.h"
+
+#include "image.h"
+
+struct program *image_program;
+#define THIS ((struct image *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)<(b)?(b):(a))
+
+/***************** internals ***********************************/
+
+#define apply_alpha(x,y,alpha) \
+   ((unsigned char)((y*(255L-(alpha))+x*(alpha))/255L))
+
+#define set_rgb_group_alpha(dest,src,alpha) \
+   ((dest).r=apply_alpha((dest).r,(src).r,alpha), \
+    (dest).g=apply_alpha((dest).g,(src).g,alpha), \
+    (dest).b=apply_alpha((dest).b,(src).b,alpha))
+
+#define pixel(_img,x,y) ((_img)->img[(x)+(y)*(_img)->xsize])
+
+#define setpixel(x,y) \
+   (THIS->alpha? \
+    set_rgb_group_alpha(THIS->img[(x)+(y)*THIS->xsize],THIS->rgb,THIS->alpha): \
+    ((pixel(THIS,x,y)=THIS->rgb),0))
+
+#define setpixel_test(x,y) \
+   (((x)<0||(y)<0||(x)>=THIS->xsize||(y)>=THIS->ysize)? \
+    0:(setpixel(x,y),0))
+
+static INLINE void getrgb(struct image *img,
+			  INT32 args_start,INT32 args,char *name)
+{
+   INT32 i;
+   if (args-args_start<3) return;
+   for (i=0; i<3; i++)
+      if (sp[-args+i+args_start].type!=T_INT)
+         error("Illegal r,g,b argument to %s\n",name);
+   img->rgb.r=(unsigned char)sp[-args+args_start].u.integer;
+   img->rgb.g=(unsigned char)sp[1-args+args_start].u.integer;
+   img->rgb.b=(unsigned char)sp[2-args+args_start].u.integer;
+   if (args-args_start>=4)
+      if (sp[3-args+args_start].type!=T_INT)
+         error("Illegal alpha argument to %s\n",name);
+      else
+         img->alpha=sp[3-args+args_start].u.integer;
+   else
+      img->alpha=0;
+}
+
+/*** end internals ***/
+
+
+void img_clear(rgb_group *dest,rgb_group rgb,INT32 size)
+{
+   while (size--) *(dest++)=rgb;
+}
+
+void img_box_nocheck(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
+{
+   INT32 x,mod;
+   rgb_group *foo,*end,rgb;
+
+   mod=THIS->xsize-(x2-x1)-1;
+   foo=THIS->img+x1+y1*THIS->xsize;
+   end=THIS->img+x2+y2*THIS->xsize;
+   rgb=THIS->rgb;
+
+   if (!THIS->alpha)
+      for (; foo<end; foo+=mod) for (x=x1; x<=x2; x++) *(foo++)=rgb;
+   else
+      for (; foo<end; foo+=mod) for (x=x1; x<=x2; x++,foo++) 
+	 set_rgb_group_alpha(*foo,rgb,THIS->alpha);
+}
+
+
+void img_blit(rgb_group *dest,rgb_group *src,INT32 width,
+	      INT32 lines,INT32 moddest,INT32 modsrc)
+{
+   while (lines--)
+   {
+      MEMCPY(dest,src,sizeof(rgb_group)*width);
+      dest+=moddest;
+      src+=modsrc;
+   }
+}
+
+void img_crop(struct image *dest,
+	      struct image *img,
+	      INT32 x1,INT32 y1,
+	      INT32 x2,INT32 y2)
+{
+   rgb_group *new;
+   INT32 blitwidth;
+   INT32 blitheight;
+
+   if (dest->img) { free(dest->img); dest->img=NULL; }
+
+   if (x1==0 && y1==0 &&
+       img->xsize-1==x2 && img->ysize-1==y2)
+   {
+      *dest=*img;
+      new=malloc( (x2-x1+1)*(y2-y1+1)*sizeof(rgb_group) + 1);
+      if (!new) 
+	error("Out of memory.\n");
+      MEMCPY(new,img->img,(x2-x1+1)*(y2-y1+1)*sizeof(rgb_group));
+      dest->img=new;
+      return;
+   }
+
+   if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
+   if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
+
+   new=malloc( (x2-x1+1)*(y2-y1+1)*sizeof(rgb_group) +1);
+   if (!new)
+     error("Out of memory.\n");
+
+   img_clear(new,THIS->rgb,(x2-x1+1)*(y2-y1+1));
+
+   blitwidth=min(x2,img->xsize-1)-max(x1,0)+1;
+   blitheight=min(y2,img->ysize-1)-max(y1,0)+1;
+
+   img_blit(new+max(0,-x1)+(x2-x1+1)*max(0,-y1),
+	    img->img+max(0,x1)+(img->xsize)*max(0,y1),
+	    blitwidth,
+	    blitheight,
+	    (x2-x1+1),
+	    img->xsize);
+
+   dest->img=new;
+   dest->xsize=x2-x1+1;
+   dest->ysize=y2-y1+1;
+}
+
+void img_clone(struct image *newimg,struct image *img)
+{
+   if (newimg->img) free(newimg->img);
+   newimg->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1);
+   if (!newimg->img) error("Out of memory!\n");
+   MEMCPY(newimg->img,img->img,sizeof(rgb_group)*img->xsize*img->ysize);
+   newimg->xsize=img->xsize;
+   newimg->ysize=img->ysize;
+   newimg->rgb=img->rgb;
+}
+
+void image_paste(INT32 args)
+{
+   struct image *img;
+   INT32 x1,y1,x2,y2,blitwidth,blitheight;
+
+   if (args<1
+       || sp[-args].type!=T_OBJECT
+       || !sp[-args].u.object
+       || sp[-args].u.object->prog!=image_program)
+      error("illegal argument 1 to image->paste()\n");
+   if (!THIS->img) return;
+
+   img=(struct image*)sp[-args].u.object->storage;
+   if (!img) return;
+
+   if (args>=3)
+   {
+      if (sp[1-args].type!=T_INT
+	  || sp[2-args].type!=T_INT)
+         error("illegal arguments to image->paste()\n");
+      x1=sp[1-args].u.integer;
+      y1=sp[2-args].u.integer;
+   }
+   else x1=y1=0;
+   pop_n_elems(args-1);
+
+   x2=x1+img->xsize-1;
+   y2=y1+img->ysize-1;
+
+   blitwidth=min(x2,THIS->xsize-1)-max(x1,0)+1;
+   blitheight=min(y2,THIS->ysize-1)-max(y1,0)+1;
+
+   img_blit(THIS->img+max(0,x1)+(THIS->xsize)*max(0,y1),
+	    img->img+max(0,-x1)+(x2-x1+1)*max(0,-y1),
+	    blitwidth,
+	    blitheight,
+	    THIS->xsize,
+	    img->xsize);
+}
+
+void image_paste_alpha(INT32 args)
+{
+   struct image *img;
+   INT32 x1,y1,x,y;
+
+   if (args<2
+       || sp[-args].type!=T_OBJECT
+       || !sp[-args].u.object
+       || sp[-args].u.object->prog!=image_program
+       || sp[1-args].type!=T_INT)
+      error("illegal arguments to image->paste_alpha()\n");
+   if (!THIS->img) return;
+
+   img=(struct image*)sp[-args].u.object->storage;
+   if (!img) return;
+   THIS->alpha=(unsigned char)(sp[1-args].u.integer);
+   
+   if (args>=4)
+   {
+      if (sp[2-args].type!=T_INT
+	  || sp[3-args].type!=T_INT)
+         error("illegal arguments to image->paste_alpha()\n");
+      x1=sp[2-args].u.integer;
+      y1=sp[3-args].u.integer;
+   }
+   else x1=y1=0;
+
+   for (x=0; x<img->xsize; x++)
+      for (y=0; y<img->ysize; y++)
+      {
+	 THIS->rgb=pixel(img,x,y);
+	 setpixel_test(x1+x,y1+y);
+      }
+
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_paste_mask(INT32 args)
+{
+   struct image *img,*mask;
+   INT32 x1,y1,x,y,x2,y2;
+
+   if (args<2)
+      error("illegal number of arguments to image->paste_mask()\n");
+   if (sp[-args].type!=T_OBJECT
+       || !sp[-args].u.object
+       || sp[-args].u.object->prog!=image_program)
+      error("illegal argument 1 to image->paste_mask()\n");
+   if (sp[1-args].type!=T_OBJECT
+       || !sp[1-args].u.object
+       || sp[1-args].u.object->prog!=image_program)
+      error("illegal argument 2 to image->paste_mask()\n");
+   if (!THIS->img) return;
+
+   img=(struct image*)sp[-args].u.object->storage;
+   mask=(struct image*)sp[1-args].u.object->storage;
+   if ((!img)||(!img->img)) error("argument 1 has no image\n");
+   if ((!mask)||(!mask->img)) error("argument 2 (alpha) has no image\n");
+   
+   if (args>=4)
+   {
+      if (sp[2-args].type!=T_INT
+	  || sp[3-args].type!=T_INT)
+         error("illegal coordinate arguments to image->paste_mask()\n");
+      x1=sp[2-args].u.integer;
+      y1=sp[3-args].u.integer;
+   }
+   else x1=y1=0;
+
+   x2=min(THIS->xsize-x1,min(img->xsize,mask->xsize));
+   y2=min(THIS->ysize-y1,min(img->ysize,mask->ysize));
+
+   for (x=max(0,-x1); x<x2; x++)
+      for (y=max(0,-y1); y<y2; y++)
+      {
+	 pixel(THIS,x+x1,y+y1).r=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).r*(long)(255-pixel(mask,x,y).r)+
+			     pixel(img,x,y).r*(long)pixel(mask,x,y).r)/255);
+	 pixel(THIS,x+x1,y+y1).g=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).g*(long)(255-pixel(mask,x,y).g)+
+			     pixel(img,x,y).g*(long)pixel(mask,x,y).g)/255);
+	 pixel(THIS,x+x1,y+y1).b=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).b*(long)(255-pixel(mask,x,y).b)+
+			     pixel(img,x,y).b*(long)pixel(mask,x,y).b)/255);
+      }
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_paste_alpha_color(INT32 args)
+{
+   struct image *img,*mask;
+   INT32 x1,y1,x,y,x2,y2;
+
+   if (args!=1 && args!=4 && args!=6 && args!=3)
+      error("illegal number of arguments to image->paste_alpha_color()\n");
+   if (sp[-args].type!=T_OBJECT
+       || !sp[-args].u.object
+       || sp[-args].u.object->prog!=image_program)
+      error("illegal argument 1 to image->paste_alpha_color()\n");
+   if (!THIS->img) return;
+
+   if (args==6 || args==4) /* colors at arg 2..4 */
+      getrgb(THIS,1,args,"image->paste_alpha_color()\n");
+   if (args==3) /* coords at 2..3 */
+   {
+      if (sp[1-args].type!=T_INT
+	  || sp[2-args].type!=T_INT)
+         error("illegal coordinate arguments to image->paste_alpha_color()\n");
+      x1=sp[1-args].u.integer;
+      y1=sp[2-args].u.integer;
+   }
+   else if (args==6) /* at 5..6 */
+   {
+      if (sp[4-args].type!=T_INT
+	  || sp[5-args].type!=T_INT)
+         error("illegal coordinate arguments to image->paste_alpha_color()\n");
+      x1=sp[4-args].u.integer;
+      y1=sp[5-args].u.integer;
+   }
+   else x1=y1=0;
+
+   mask=(struct image*)sp[-args].u.object->storage;
+   if (!mask||!mask->img) error("argument 2 (alpha) has no image\n");
+   
+   x2=min(THIS->xsize-x1,mask->xsize);
+   y2=min(THIS->ysize-y1,mask->ysize);
+
+   for (x=max(0,-x1); x<x2; x++)
+      for (y=max(0,-y1); y<y2; y++)
+      {
+	 pixel(THIS,x+x1,y+y1).r=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).r*(long)(255-pixel(mask,x,y).r)+
+			     THIS->rgb.r*(long)pixel(mask,x,y).r)/255);
+	 pixel(THIS,x+x1,y+y1).g=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).g*(long)(255-pixel(mask,x,y).g)+
+			     THIS->rgb.g*(long)pixel(mask,x,y).g)/255);
+	 pixel(THIS,x+x1,y+y1).b=
+            (unsigned char)((pixel(THIS,x+x1,y+y1).b*(long)(255-pixel(mask,x,y).b)+
+			     THIS->rgb.b*(long)pixel(mask,x,y).b)/255);
+      }
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void img_box(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
+{   
+   if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
+   if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
+   if (x2<0||y2<0||x1>=THIS->xsize||y1>=THIS->ysize) return;
+   img_box_nocheck(max(x1,0),max(y1,0),min(x2,THIS->xsize-1),min(y2,THIS->ysize-1));
+}
+
diff --git a/src/modules/image/image.c b/src/modules/image/image.c
index a79178bc22b2094d628ac92c356f9ce18bb6671d..f9229b05d6033af77c2545b28527a217f31c31a4 100644
--- a/src/modules/image/image.c
+++ b/src/modules/image/image.c
@@ -15,6 +15,7 @@
 #include "error.h"
 
 #include "image.h"
+#include "builtin_functions.h"
 
 struct program *image_program;
 #define THIS ((struct image *)(fp->current_storage))
@@ -22,6 +23,7 @@ struct program *image_program;
 
 #define min(a,b) ((a)<(b)?(a):(b))
 #define max(a,b) ((a)<(b)?(b):(a))
+#define testrange(x) max(min((x),255),0)
 
 #define sq(x) ((x)*(x))
 
@@ -101,97 +103,6 @@ static INLINE void getrgbl(rgbl_group *rgb,INT32 args_start,INT32 args,char *nam
    rgb->b=sp[2-args+args_start].u.integer;
 }
 
-static void img_clear(rgb_group *dest,rgb_group rgb,INT32 size)
-{
-   while (size--) *(dest++)=rgb;
-}
-
-static INLINE void img_box_nocheck(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
-{
-   INT32 x,mod;
-   rgb_group *foo,*end,rgb;
-
-   mod=THIS->xsize-(x2-x1)-1;
-   foo=THIS->img+x1+y1*THIS->xsize;
-   end=THIS->img+x2+y2*THIS->xsize;
-   rgb=THIS->rgb;
-
-   if (!THIS->alpha)
-      for (; foo<end; foo+=mod) for (x=x1; x<=x2; x++) *(foo++)=rgb;
-   else
-      for (; foo<end; foo+=mod) for (x=x1; x<=x2; x++,foo++) 
-	 set_rgb_group_alpha(*foo,rgb,THIS->alpha);
-}
-
-static INLINE void img_blit(rgb_group *dest,rgb_group *src,INT32 width,
-			    INT32 lines,INT32 moddest,INT32 modsrc)
-{
-   while (lines--)
-   {
-      MEMCPY(dest,src,sizeof(rgb_group)*width);
-      dest+=moddest;
-      src+=modsrc;
-   }
-}
-
-static void img_crop(struct image *dest,
-		     struct image *img,
-		     INT32 x1,INT32 y1,
-		     INT32 x2,INT32 y2)
-{
-   rgb_group *new;
-   INT32 blitwidth;
-   INT32 blitheight;
-
-   if (dest->img) { free(dest->img); dest->img=NULL; }
-
-   if (x1==0 && y1==0 &&
-       img->xsize-1==x2 && img->ysize-1==y2)
-   {
-      *dest=*img;
-      new=malloc( (x2-x1+1)*(y2-y1+1)*sizeof(rgb_group) + 1);
-      if (!new) 
-	error("Out of memory.\n");
-      MEMCPY(new,img->img,(x2-x1+1)*(y2-y1+1)*sizeof(rgb_group));
-      dest->img=new;
-      return;
-   }
-
-   if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
-   if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
-
-   new=malloc( (x2-x1+1)*(y2-y1+1)*sizeof(rgb_group) +1);
-   if (!new)
-     error("Out of memory.\n");
-
-   img_clear(new,THIS->rgb,(x2-x1+1)*(y2-y1+1));
-
-   blitwidth=min(x2,img->xsize-1)-max(x1,0)+1;
-   blitheight=min(y2,img->ysize-1)-max(y1,0)+1;
-
-   img_blit(new+max(0,-x1)+(x2-x1+1)*max(0,-y1),
-	    img->img+max(0,x1)+(img->xsize)*max(0,y1),
-	    blitwidth,
-	    blitheight,
-	    (x2-x1+1),
-	    img->xsize);
-
-   dest->img=new;
-   dest->xsize=x2-x1+1;
-   dest->ysize=y2-y1+1;
-}
-
-static INLINE void img_clone(struct image *newimg,struct image *img)
-{
-   if (newimg->img) free(newimg->img);
-   newimg->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1);
-   if (!newimg->img) error("Out of memory!\n");
-   MEMCPY(newimg->img,img->img,sizeof(rgb_group)*img->xsize*img->ysize);
-   newimg->xsize=img->xsize;
-   newimg->ysize=img->ysize;
-   newimg->rgb=img->rgb;
-}
-
 static INLINE void img_line(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
 {   
    INT32 pixelstep,pos;
@@ -241,242 +152,6 @@ static INLINE void img_line(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
    }
 }
 
-
-static INLINE void img_box(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
-{   
-   if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
-   if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
-   if (x2<0||y2<0||x1>=THIS->xsize||y1>=THIS->ysize) return;
-   img_box_nocheck(max(x1,0),max(y1,0),min(x2,THIS->xsize-1),min(y2,THIS->ysize-1));
-}
-
-#define decimals(x) ((x)-(int)(x))
-#define testrange(x) max(min((x),255),0)
-#define _scale_add_rgb(dest,src,factor) \
-   ((dest).r=testrange((dest).r+(int)((src).r*(factor)+0.5)), \
-    (dest).g=testrange((dest).g+(int)((src).g*(factor)+0.5)), \
-    (dest).b=testrange((dest).b+(int)((src).b*(factor)+0.5))) 
-#define scale_add_pixel(dest,dx,dy,dxw,src,sx,sy,sxw,factor) \
-   _scale_add_rgb(dest[(dx)+(dy)*(dxw)],src[(sx)+(sy)*(sxw)],factor)
-
-static INLINE void scale_add_line(rgb_group *new,INT32 yn,INT32 newx,
-				  rgb_group *img,INT32 y,INT32 xsize,
-				  double py,double dx)
-{
-   INT32 x,xd;
-   double xn;
-   for (x=0,xn=0; x<THIS->xsize; x++,xn+=dx)
-   {
-      if ((INT32)xn<(INT32)(xn+dx))
-      {
-         scale_add_pixel(new,(INT32)xn,yn,newx,img,x,y,xsize,py*(1.0-decimals(xn)));
-	 if ((xd=(INT32)(xn+dx)-(INT32)(xn))>1) 
-            while (--xd)
-               scale_add_pixel(new,(INT32)xn+xd,yn,newx,img,x,y,xsize,py);
-	 scale_add_pixel(new,(INT32)(xn+dx),yn,newx,img,x,y,xsize,py*decimals(xn+dx));
-      }
-      else
-         scale_add_pixel(new,(int)xn,yn,newx,img,x,y,xsize,py*dx);
-   }
-}
-
-static void img_scale(struct image *dest,
-		      struct image *source,
-		      INT32 newx,INT32 newy)
-{
-   rgb_group *new;
-   INT32 y,yd;
-   double yn,dx,dy;
-
-   if (dest->img) { free(dest->img); dest->img=NULL; }
-
-   if (!THIS->img || newx<=0 || newy<=0) return; /* no way */
-   new=malloc(newx*newy*sizeof(rgb_group) +1);
-   if (!new) error("Out of memory!\n");
-
-   MEMSET(new,0,newx*newy*sizeof(rgb_group));
-   
-   dx=((double)newx-0.000001)/source->xsize; 
-   dy=((double)newy-0.000001)/source->ysize; 
-
-   for (y=0,yn=0; y<source->ysize; y++,yn+=dy)
-   {
-      if ((INT32)yn<(INT32)(yn+dy))
-      {
-	 scale_add_line(new,(INT32)(yn),newx,source->img,y,source->xsize,
-			(1.0-decimals(yn)),dx);
-	 if ((yd=(INT32)(yn+dy)-(INT32)(yn))>1) 
-            while (--yd)
-   	       scale_add_line(new,(INT32)yn+yd,newx,source->img,y,source->xsize,
-			      1.0,dx);
-	 scale_add_line(new,(INT32)(yn+dy),newx,source->img,y,source->xsize,
-			(decimals(yn+dy)),dx);
-      }
-      else
-	 scale_add_line(new,(INT32)yn,newx,source->img,y,source->xsize,
-			dy,dx);
-   }
-
-   dest->img=new;
-   dest->xsize=newx;
-   dest->ysize=newy;
-}
-
-/* Special, faster, case for scale=1/2 */
-static void img_scale2(struct image *dest, struct image *source)
-{
-   rgb_group *new;
-   INT32 x, y, newx, newy;
-   newx = source->xsize >> 1;
-   newy = source->ysize >> 1;
-   
-   if (dest->img) { free(dest->img); dest->img=NULL; }
-   if (!THIS->img || newx<=0 || newy<=0) return; /* no way */
-   new=malloc(newx*newy*sizeof(rgb_group) +1);
-   if (!new) error("Out of memory\n");
-   MEMSET(new,0,newx*newy*sizeof(rgb_group));
-
-   dest->img=new;
-   dest->xsize=newx;
-   dest->ysize=newy;
-   for (y = 0; y < newy; y++)
-     for (x = 0; x < newx; x++) {
-       pixel(dest,x,y).r = (COLOURTYPE)
-	                    (((INT32) pixel(source,2*x+0,2*y+0).r+
-			      (INT32) pixel(source,2*x+1,2*y+0).r+
-			      (INT32) pixel(source,2*x+0,2*y+1).r+
-			      (INT32) pixel(source,2*x+1,2*y+1).r) >> 2);
-       pixel(dest,x,y).g = (COLOURTYPE)
-	                    (((INT32) pixel(source,2*x+0,2*y+0).g+
-			      (INT32) pixel(source,2*x+1,2*y+0).g+
-			      (INT32) pixel(source,2*x+0,2*y+1).g+
-			      (INT32) pixel(source,2*x+1,2*y+1).g) >> 2);
-       pixel(dest,x,y).b = (COLOURTYPE)
-	                    (((INT32) pixel(source,2*x+0,2*y+0).b+
-			      (INT32) pixel(source,2*x+1,2*y+0).b+
-			      (INT32) pixel(source,2*x+0,2*y+1).b+
-			      (INT32) pixel(source,2*x+1,2*y+1).b) >> 2);
-     }
-}
-
-static INLINE unsigned char getnext(struct pike_string *s,INT32 *pos)
-{
-   if (*pos>=s->len) return 0;
-   if (s->str[(*pos)]=='#')
-      for (;*pos<s->len && ISSPACE(s->str[*pos]);(*pos)++);
-   return s->str[(*pos)++];
-}
-
-static INLINE void skip_to_eol(struct pike_string *s,INT32 *pos)
-{
-   for (;*pos<s->len && s->str[*pos]!=10;(*pos)++);
-}
-
-static INLINE unsigned char getnext_skip_comment(struct pike_string *s,INT32 *pos)
-{
-   unsigned char c;
-   while ((c=getnext(s,pos))=='#')
-      skip_to_eol(s,pos);
-   return c;
-}
-
-static INLINE void skipwhite(struct pike_string *s,INT32 *pos)
-{
-   while (*pos<s->len && 
-	  ( ISSPACE(s->str[*pos]) ||
-	    s->str[*pos]=='#'))
-      getnext_skip_comment(s,pos);
-}
-
-static INLINE INT32 getnextnum(struct pike_string *s,INT32 *pos)
-{
-   INT32 i;
-   skipwhite(s,pos);
-   i=0;
-   while (*pos<s->len &&
-	  s->str[*pos]>='0' && s->str[*pos]<='9')
-   {
-      i=(i*10)+s->str[*pos]-'0';
-      getnext(s,pos);
-   }
-   return i;
-}
-
-static char* img_frompnm(struct pike_string *s)
-{
-   struct image new;
-   INT32 type,c=0,maxval=255;
-   INT32 pos=0,x,y,i;
-
-   skipwhite(s,&pos);
-   if (getnext(s,&pos)!='P') return "not pnm"; /* not pnm */
-   type=getnext(s,&pos);
-   if (type<'1'||type>'6') return "unknown type"; /* unknown type */
-   new.xsize=getnextnum(s,&pos);
-   new.ysize=getnextnum(s,&pos);
-   if (new.xsize<=0||new.ysize<=0) return "illegal size"; /* illegal size */
-   if (type=='3'||type=='2'||type=='6'||type=='5')
-      maxval=getnextnum(s,&pos);
-   new.img=malloc(new.xsize*new.ysize*sizeof(rgb_group)+1);
-   if (!new.img) error("Out of memory.\n");
-
-   if (type=='1'||type=='2'||type=='3')
-   {
-     skipwhite(s,&pos);
-   }
-   else
-   {
-     skip_to_eol(s,&pos);
-     pos++;
-   }
-   for (y=0; y<new.ysize; y++)
-   {
-      for (i=0,x=0; x<new.xsize; x++)
-      {
-         switch (type)
-	 {
-	    case '1':
-	       c=getnextnum(s,&pos);
-               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
-	          (unsigned char)~(c*255);
-	       break;
-	    case '2':
-	       c=getnextnum(s,&pos);
-               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
-	          (unsigned char)((c*255L)/maxval);
-	       break;
-	    case '3':
-	       pixel(&new,x,y).r=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
-	       pixel(&new,x,y).g=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
-	       pixel(&new,x,y).b=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
-	       break;
-	    case '4':
-	       if (!i) c=getnext(s,&pos),i=8;
-               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
-	          (unsigned char)~(((c>>7)&1)*255);
-	       c<<=1;
-	       i--;
-	       break;
-	    case '5':
-	       c=getnext(s,&pos);
-               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
-	          (unsigned char)((c*255L)/maxval);
-	       break;
-	    case '6':
-	       pixel(&new,x,y).r=(unsigned char)((getnext(s,&pos)*255L)/maxval);
-	       pixel(&new,x,y).g=(unsigned char)((getnext(s,&pos)*255L)/maxval);
-	       pixel(&new,x,y).b=(unsigned char)((getnext(s,&pos)*255L)/maxval);
-	       break;
-	 }
-      }
-   }
-   if (THIS->img) free(THIS->img);
-   THIS->xsize=new.xsize;
-   THIS->ysize=new.ysize;
-   THIS->img=new.img;
-   return NULL;
-}
-
 static INLINE rgb_group _pixel_apply_matrix(struct image *img,
 					    int x,int y,
 					    int width,int height,
@@ -524,7 +199,8 @@ static INLINE rgb_group _pixel_apply_matrix(struct image *img,
    return res;
 }
 
-static void img_apply_matrix(struct image *dest,
+
+void img_apply_matrix(struct image *dest,
 		      struct image *img,
 		      int width,int height,
 		      rgbl_group *matrix,
@@ -714,21 +390,6 @@ void image_clear(INT32 args)
    push_object(o);
 }
 
-void image_toppm(INT32 args)
-{
-   char buf[80];
-   struct pike_string *a,*b;
-   
-   pop_n_elems(args);
-   if (!THIS->img) { error("no image\n");  return; }
-   sprintf(buf,"P6\n%d %d\n255\n",THIS->xsize,THIS->ysize);
-   a=make_shared_string(buf);
-   b=make_shared_binary_string((char*)THIS->img,
-			       THIS->xsize*THIS->ysize*3);
-   push_string(add_shared_strings(a,b));
-   free_string(a);
-   free_string(b);
-}
 
 void image_fromgif(INT32 args)
 {
@@ -747,8 +408,6 @@ void image_fromgif(INT32 args)
 
 void image_togif(INT32 args)
 {
-   char buf[80];
-   struct pike_string *a,*b;
    rgb_group *transparent=NULL;
    struct colortable *ct;
 
@@ -767,8 +426,6 @@ void image_togif(INT32 args)
 
 void image_togif_fs(INT32 args)
 {
-   char buf[80];
-   struct pike_string *a,*b;
    rgb_group *transparent=NULL;
    struct colortable *ct;
 
@@ -785,17 +442,6 @@ void image_togif_fs(INT32 args)
    colortable_free(ct);
 }
 
-void image_frompnm(INT32 args)
-{
-   char *s;
-   if (args<1||
-       sp[-args].type!=T_STRING)
-      error("Illegal argument to image->frompnm()\n");
-   s=img_frompnm(sp[-args].u.string);
-   pop_n_elems(args);
-   if (!s) { push_object(THISOBJ); THISOBJ->refs++; }
-   else push_string(make_shared_string(s));
-}
 
 void image_copy(INT32 args)
 {
@@ -914,194 +560,6 @@ void image_autocrop(INT32 args)
    push_object(o);
 }
 
-void image_paste(INT32 args)
-{
-   struct image *img;
-   INT32 x1,y1,x2,y2,blitwidth,blitheight;
-
-   if (args<1
-       || sp[-args].type!=T_OBJECT
-       || !sp[-args].u.object
-       || sp[-args].u.object->prog!=image_program)
-      error("illegal argument 1 to image->paste()\n");
-   if (!THIS->img) return;
-
-   img=(struct image*)sp[-args].u.object->storage;
-   if (!img) return;
-
-   if (args>=3)
-   {
-      if (sp[1-args].type!=T_INT
-	  || sp[2-args].type!=T_INT)
-         error("illegal arguments to image->paste()\n");
-      x1=sp[1-args].u.integer;
-      y1=sp[2-args].u.integer;
-   }
-   else x1=y1=0;
-   pop_n_elems(args-1);
-
-   x2=x1+img->xsize-1;
-   y2=y1+img->ysize-1;
-
-   blitwidth=min(x2,THIS->xsize-1)-max(x1,0)+1;
-   blitheight=min(y2,THIS->ysize-1)-max(y1,0)+1;
-
-   img_blit(THIS->img+max(0,x1)+(THIS->xsize)*max(0,y1),
-	    img->img+max(0,-x1)+(x2-x1+1)*max(0,-y1),
-	    blitwidth,
-	    blitheight,
-	    THIS->xsize,
-	    img->xsize);
-}
-
-void image_paste_alpha(INT32 args)
-{
-   struct image *img;
-   INT32 x1,y1,x,y;
-
-   if (args<2
-       || sp[-args].type!=T_OBJECT
-       || !sp[-args].u.object
-       || sp[-args].u.object->prog!=image_program
-       || sp[1-args].type!=T_INT)
-      error("illegal arguments to image->paste_alpha()\n");
-   if (!THIS->img) return;
-
-   img=(struct image*)sp[-args].u.object->storage;
-   if (!img) return;
-   THIS->alpha=(unsigned char)(sp[1-args].u.integer);
-   
-   if (args>=4)
-   {
-      if (sp[2-args].type!=T_INT
-	  || sp[3-args].type!=T_INT)
-         error("illegal arguments to image->paste_alpha()\n");
-      x1=sp[2-args].u.integer;
-      y1=sp[3-args].u.integer;
-   }
-   else x1=y1=0;
-
-   for (x=0; x<img->xsize; x++)
-      for (y=0; y<img->ysize; y++)
-      {
-	 THIS->rgb=pixel(img,x,y);
-	 setpixel_test(x1+x,y1+y);
-      }
-
-   pop_n_elems(args);
-   THISOBJ->refs++;
-   push_object(THISOBJ);
-}
-
-void image_paste_mask(INT32 args)
-{
-   struct image *img,*mask;
-   INT32 x1,y1,x,y,x2,y2;
-
-   if (args<2)
-      error("illegal number of arguments to image->paste_mask()\n");
-   if (sp[-args].type!=T_OBJECT
-       || !sp[-args].u.object
-       || sp[-args].u.object->prog!=image_program)
-      error("illegal argument 1 to image->paste_mask()\n");
-   if (sp[1-args].type!=T_OBJECT
-       || !sp[1-args].u.object
-       || sp[1-args].u.object->prog!=image_program)
-      error("illegal argument 2 to image->paste_mask()\n");
-   if (!THIS->img) return;
-
-   img=(struct image*)sp[-args].u.object->storage;
-   mask=(struct image*)sp[1-args].u.object->storage;
-   if ((!img)||(!img->img)) error("argument 1 has no image\n");
-   if ((!mask)||(!mask->img)) error("argument 2 (alpha) has no image\n");
-   
-   if (args>=4)
-   {
-      if (sp[2-args].type!=T_INT
-	  || sp[3-args].type!=T_INT)
-         error("illegal coordinate arguments to image->paste_mask()\n");
-      x1=sp[2-args].u.integer;
-      y1=sp[3-args].u.integer;
-   }
-   else x1=y1=0;
-
-   x2=min(THIS->xsize-x1,min(img->xsize,mask->xsize));
-   y2=min(THIS->ysize-y1,min(img->ysize,mask->ysize));
-
-   for (x=max(0,-x1); x<x2; x++)
-      for (y=max(0,-y1); y<y2; y++)
-      {
-	 pixel(THIS,x+x1,y+y1).r=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).r*(long)(255-pixel(mask,x,y).r)+
-			     pixel(img,x,y).r*(long)pixel(mask,x,y).r)/255);
-	 pixel(THIS,x+x1,y+y1).g=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).g*(long)(255-pixel(mask,x,y).g)+
-			     pixel(img,x,y).g*(long)pixel(mask,x,y).g)/255);
-	 pixel(THIS,x+x1,y+y1).b=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).b*(long)(255-pixel(mask,x,y).b)+
-			     pixel(img,x,y).b*(long)pixel(mask,x,y).b)/255);
-      }
-   pop_n_elems(args);
-   THISOBJ->refs++;
-   push_object(THISOBJ);
-}
-
-void image_paste_alpha_color(INT32 args)
-{
-   struct image *img,*mask;
-   INT32 x1,y1,x,y,x2,y2;
-
-   if (args!=1 && args!=4 && args!=6 && args!=3)
-      error("illegal number of arguments to image->paste_alpha_color()\n");
-   if (sp[-args].type!=T_OBJECT
-       || !sp[-args].u.object
-       || sp[-args].u.object->prog!=image_program)
-      error("illegal argument 1 to image->paste_alpha_color()\n");
-   if (!THIS->img) return;
-
-   if (args==6 || args==4) /* colors at arg 2..4 */
-      getrgb(THIS,1,args,"image->paste_alpha_color()\n");
-   if (args==3) /* coords at 2..3 */
-   {
-      if (sp[1-args].type!=T_INT
-	  || sp[2-args].type!=T_INT)
-         error("illegal coordinate arguments to image->paste_alpha_color()\n");
-      x1=sp[1-args].u.integer;
-      y1=sp[2-args].u.integer;
-   }
-   else if (args==6) /* at 5..6 */
-   {
-      if (sp[4-args].type!=T_INT
-	  || sp[5-args].type!=T_INT)
-         error("illegal coordinate arguments to image->paste_alpha_color()\n");
-      x1=sp[4-args].u.integer;
-      y1=sp[5-args].u.integer;
-   }
-   else x1=y1=0;
-
-   mask=(struct image*)sp[-args].u.object->storage;
-   if (!mask||!mask->img) error("argument 2 (alpha) has no image\n");
-   
-   x2=min(THIS->xsize-x1,mask->xsize);
-   y2=min(THIS->ysize-y1,mask->ysize);
-
-   for (x=max(0,-x1); x<x2; x++)
-      for (y=max(0,-y1); y<y2; y++)
-      {
-	 pixel(THIS,x+x1,y+y1).r=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).r*(long)(255-pixel(mask,x,y).r)+
-			     THIS->rgb.r*(long)pixel(mask,x,y).r)/255);
-	 pixel(THIS,x+x1,y+y1).g=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).g*(long)(255-pixel(mask,x,y).g)+
-			     THIS->rgb.g*(long)pixel(mask,x,y).g)/255);
-	 pixel(THIS,x+x1,y+y1).b=
-            (unsigned char)((pixel(THIS,x+x1,y+y1).b*(long)(255-pixel(mask,x,y).b)+
-			     THIS->rgb.b*(long)pixel(mask,x,y).b)/255);
-      }
-   pop_n_elems(args);
-   THISOBJ->refs++;
-   push_object(THISOBJ);
-}
 
 void image_setcolor(INT32 args)
 {
@@ -1226,62 +684,6 @@ void image_circle(INT32 args)
    push_object(THISOBJ);
 }
 
-void image_scale(INT32 args)
-{
-   float factor;
-   struct object *o;
-   struct image *newimg;
-   
-   o=clone(image_program,0);
-   newimg=(struct image*)(o->storage);
-
-   if (args==1 && sp[-args].type==T_FLOAT) {
-     if (sp[-args].u.float_number == 0.5)
-       img_scale2(newimg,THIS);
-     else
-       img_scale(newimg,THIS,
-		 (INT32)(THIS->xsize*sp[-args].u.float_number),
-		 (INT32)(THIS->ysize*sp[-args].u.float_number));
-   }
-   else if (args>=2 &&
-	    sp[-args].type==T_INT && sp[-args].u.integer==0 &&
-	    sp[1-args].type==T_INT)
-   {
-      factor=((float)sp[1-args].u.integer)/THIS->ysize;
-      img_scale(newimg,THIS,
-		(INT32)(THIS->xsize*factor),
-		sp[1-args].u.integer);
-   }
-   else if (args>=2 &&
-	    sp[1-args].type==T_INT && sp[1-args].u.integer==0 &&
-	    sp[-args].type==T_INT)
-   {
-      factor=((float)sp[-args].u.integer)/THIS->xsize;
-      img_scale(newimg,THIS,
-		sp[-args].u.integer,
-		(INT32)(THIS->ysize*factor));
-   }
-   else if (args>=2 &&
-	    sp[-args].type==T_FLOAT &&
-	    sp[1-args].type==T_FLOAT)
-      img_scale(newimg,THIS,
-		(INT32)(THIS->xsize*sp[-args].u.float_number),
-		(INT32)(THIS->ysize*sp[1-args].u.float_number));
-   else if (args>=2 &&
-	    sp[-args].type==T_INT &&
-	    sp[1-args].type==T_INT)
-      img_scale(newimg,THIS,
-		sp[-args].u.integer,
-		sp[1-args].u.integer);
-   else
-   {
-      free_object(o);
-      error("illegal arguments to image->scale()\n");
-   }
-   pop_n_elems(args);
-   push_object(o);
-}
-
 static INLINE void get_rgba_group_from_array_index(rgba_group *rgba,struct array *v,INT32 index)
 {
    struct svalue s,s2;
@@ -1335,7 +737,6 @@ void image_tuned_box(INT32 args)
    INT32 x1,y1,x2,y2,xw,yw,x,y;
    rgba_group topleft,topright,bottomleft,bottomright,sum,sumzero={0,0,0,0};
    rgb_group *img;
-   INT32 mod;
 
    if (args<5||
        sp[-args].type!=T_INT||
@@ -1732,8 +1133,6 @@ fprintf(stderr," %d,%d,%d)\n",src[x+y*xsize].r,src[x+y*xsize].g,src[x+y*xsize].b
 
 void image_select_from(INT32 args)
 {
-   INT32 i;
-   rgb_group rgb;
    struct object *o;
    struct image *img;
    INT32 low_limit;
@@ -1985,7 +1384,6 @@ static void image_map_closest(INT32 args)
    struct colortable *ct;
    long i;
    rgb_group *rgb;
-   int colors;
 
    if (!THIS->img) error("no image\n");
    if (args<1
@@ -2051,7 +1449,6 @@ static void image_map_fs(INT32 args)
 
 void image_select_colors(INT32 args)
 {
-   rgb_group *transparent=NULL;
    struct colortable *ct;
    int colors,i;
 
@@ -2075,143 +1472,6 @@ void image_select_colors(INT32 args)
    colortable_free(ct);
 }
 
-static void image_ccw(INT32 args)
-{
-   INT32 i,j;
-   rgb_group *src,*dest;
-   struct object *o;
-   struct image *img;
-
-   pop_n_elems(args);
-
-   if (!THIS->img) error("no image\n");
-
-   o=clone(image_program,0);
-   img=(struct image*)o->storage;
-   *img=*THIS;
-   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
-   {
-      free_object(o);
-      error("Out of memory\n");
-   }
-   img->xsize=THIS->ysize;
-   img->ysize=THIS->xsize;
-   i=THIS->xsize;
-   src=THIS->img+THIS->xsize-1;
-   dest=img->img;
-   while (i--)
-   {
-      j=THIS->ysize;
-      while (j--) *(dest++)=*(src),src+=THIS->xsize;
-      src--;
-      src-=THIS->xsize*THIS->ysize;
-   }
-
-   push_object(o);
-}
-
-static void image_cw(INT32 args)
-{
-   INT32 i,j;
-   rgb_group *src,*dest;
-   struct object *o;
-   struct image *img;
-
-   pop_n_elems(args);
-
-   if (!THIS->img) error("no image\n");
-
-   o=clone(image_program,0);
-   img=(struct image*)o->storage;
-   *img=*THIS;
-   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
-   {
-      free_object(o);
-      error("Out of memory\n");
-   }
-   img->xsize=THIS->ysize;
-   img->ysize=THIS->xsize;
-   i=THIS->xsize;
-   src=THIS->img+THIS->xsize-1;
-   dest=img->img+THIS->xsize*THIS->ysize;
-   while (i--)
-   {
-      j=THIS->ysize;
-      while (j--) *(--dest)=*(src),src+=THIS->xsize;
-      src--;
-      src-=THIS->xsize*THIS->ysize;
-   }
-
-   push_object(o);
-}
-
-static void image_mirrorx(INT32 args)
-{
-   rgb_group *src,*dest;
-   struct object *o;
-   struct image *img;
-   INT32 i,j;
-
-   pop_n_elems(args);
-
-   if (!THIS->img) error("no image\n");
-
-   o=clone(image_program,0);
-   img=(struct image*)o->storage;
-   *img=*THIS;
-   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
-   {
-      free_object(o);
-      error("Out of memory\n");
-   }
-   
-   i=THIS->ysize;
-   src=THIS->img+THIS->xsize-1;
-   dest=img->img;
-   while (i--)
-   {
-      j=THIS->xsize;
-      while (j--) *(dest++)=*(src--);
-      src+=THIS->xsize*2;
-   }
-
-   push_object(o);
-}
-
-static void image_mirrory(INT32 args)
-{
-   rgb_group *src,*dest;
-   struct object *o;
-   struct image *img;
-   INT32 i,j;
-
-   pop_n_elems(args);
-
-   if (!THIS->img) error("no image\n");
-
-   o=clone(image_program,0);
-   img=(struct image*)o->storage;
-   *img=*THIS;
-   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
-   {
-      free_object(o);
-      error("Out of memory\n");
-   }
-   
-   i=THIS->ysize;
-   src=THIS->img+THIS->xsize*(THIS->ysize-1);
-   dest=img->img;
-   while (i--)
-   {
-      j=THIS->xsize;
-      while (j--) *(dest++)=*(src++);
-      src-=THIS->xsize*2;
-   }
-
-   push_object(o);
- 
-}
-
 /***************** global init etc *****************************/
 
 #define RGB_TYPE "int|void,int|void,int|void,int|void"
@@ -2303,6 +1563,19 @@ void init_image_programs()
 		"function(:object)",0);
    add_function("mirrory",image_mirrory,
 		"function(:object)",0);
+   add_function("skewx",image_skewx,
+		"function(int|float,"RGB_TYPE":object)",0);
+   add_function("skewy",image_skewy,
+		"function(int|float,"RGB_TYPE":object)",0);
+   add_function("skewx_expand",image_skewx_expand,
+		"function(int|float,"RGB_TYPE":object)",0);
+   add_function("skewy_expand",image_skewy_expand,
+		"function(int|float,"RGB_TYPE":object)",0);
+
+   add_function("rotate",image_rotate,
+		"function(int|float,"RGB_TYPE":object)",0);
+   add_function("rotate_expand",image_rotate_expand,
+		"function(int|float,"RGB_TYPE":object)",0);
 
    add_function("xsize",image_xsize,
 		"function(:int)",0);
diff --git a/src/modules/image/image.h b/src/modules/image/image.h
index 8540e77cead4807e857bff4dfa729b8c18a476d4..c577f5d54762a20b65519234542a7c186f5d1cf2 100644
--- a/src/modules/image/image.h
+++ b/src/modules/image/image.h
@@ -71,3 +71,40 @@ void image_floyd_steinberg(rgb_group *rgb,int xsize,
 
 int image_decode_gif(struct image *dest,struct image *dest_alpha,
 		     unsigned char *src,unsigned long len);
+
+/* blit.c */
+
+void img_clear(rgb_group *dest,rgb_group rgb,INT32 size);
+void img_box_nocheck(INT32 x1,INT32 y1,INT32 x2,INT32 y2);
+void img_box(INT32 x1,INT32 y1,INT32 x2,INT32 y2);
+void img_blit(rgb_group *dest,rgb_group *src,INT32 width,
+	      INT32 lines,INT32 moddest,INT32 modsrc);
+void img_crop(struct image *dest,
+	      struct image *img,
+	      INT32 x1,INT32 y1,
+	      INT32 x2,INT32 y2);
+void img_clone(struct image *newimg,struct image *img);
+void image_paste(INT32 args);
+void image_paste_alpha(INT32 args);
+void image_paste_mask(INT32 args);
+void image_paste_alpha_color(INT32 args);
+
+/* matrix.c */
+
+void image_scale(INT32 args);
+void image_skewx(INT32 args);
+void image_skewy(INT32 args);
+void image_skewx_expand(INT32 args);
+void image_skewy_expand(INT32 args);
+void image_rotate(INT32 args);
+void image_rotate_expand(INT32 args);
+void image_cw(INT32 args);
+void image_ccw(INT32 args);
+void image_ccw(INT32 args);
+void image_mirrorx(INT32 args);
+void image_mirrory(INT32 args);
+
+/* pnm.c */
+
+void image_toppm(INT32 args);
+void image_frompnm(INT32 args);
diff --git a/src/modules/image/matrix.c b/src/modules/image/matrix.c
new file mode 100644
index 0000000000000000000000000000000000000000..2153b3c36bdea8102e4a962647cc4d228f397846
--- /dev/null
+++ b/src/modules/image/matrix.c
@@ -0,0 +1,759 @@
+#include "global.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "stralloc.h"
+#include "global.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "array.h"
+#include "error.h"
+
+#include "image.h"
+
+struct program *image_program;
+#define THIS ((struct image *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)<(b)?(b):(a))
+
+/***************** internals ***********************************/
+
+#define apply_alpha(x,y,alpha) \
+   ((unsigned char)((y*(255L-(alpha))+x*(alpha))/255L))
+
+#define set_rgb_group_alpha(dest,src,alpha) \
+   ((dest).r=apply_alpha((dest).r,(src).r,alpha), \
+    (dest).g=apply_alpha((dest).g,(src).g,alpha), \
+    (dest).b=apply_alpha((dest).b,(src).b,alpha))
+
+#define pixel(_img,x,y) ((_img)->img[(x)+(y)*(_img)->xsize])
+
+#define setpixel(x,y) \
+   (THIS->alpha? \
+    set_rgb_group_alpha(THIS->img[(x)+(y)*THIS->xsize],THIS->rgb,THIS->alpha): \
+    ((pixel(THIS,x,y)=THIS->rgb),0))
+
+#define setpixel_test(x,y) \
+   (((x)<0||(y)<0||(x)>=THIS->xsize||(y)>=THIS->ysize)? \
+    0:(setpixel(x,y),0))
+
+static INLINE int getrgb(struct image *img,
+			  INT32 args_start,INT32 args,char *name)
+{
+   INT32 i;
+   if (args-args_start<3) return 0;
+   for (i=0; i<3; i++)
+      if (sp[-args+i+args_start].type!=T_INT)
+         error("Illegal r,g,b argument to %s\n",name);
+   img->rgb.r=(unsigned char)sp[-args+args_start].u.integer;
+   img->rgb.g=(unsigned char)sp[1-args+args_start].u.integer;
+   img->rgb.b=(unsigned char)sp[2-args+args_start].u.integer;
+   if (args-args_start>=4)
+      if (sp[3-args+args_start].type!=T_INT)
+         error("Illegal alpha argument to %s\n",name);
+      else
+         img->alpha=sp[3-args+args_start].u.integer;
+   else
+      img->alpha=0;
+   return 1;
+}
+
+static INLINE int getrgbl(rgbl_group *rgb,INT32 args_start,INT32 args,char *name)
+{
+   INT32 i;
+   if (args-args_start<3) return 0;
+   for (i=0; i<3; i++)
+      if (sp[-args+i+args_start].type!=T_INT)
+         error("Illegal r,g,b argument to %s\n",name);
+   rgb->r=sp[-args+args_start].u.integer;
+   rgb->g=sp[1-args+args_start].u.integer;
+   rgb->b=sp[2-args+args_start].u.integer;
+   return 1;
+}
+
+/** end internals **/
+
+
+#define decimals(x) ((x)-(int)(x))
+#define testrange(x) max(min((x),255),0)
+#define _scale_add_rgb(dest,src,factor) \
+   ((dest).r=testrange((dest).r+(int)((src).r*(factor)+0.5)), \
+    (dest).g=testrange((dest).g+(int)((src).g*(factor)+0.5)), \
+    (dest).b=testrange((dest).b+(int)((src).b*(factor)+0.5))) 
+#define scale_add_pixel(dest,dx,dy,dxw,src,sx,sy,sxw,factor) \
+   _scale_add_rgb(dest[(dx)+(dy)*(dxw)],src[(sx)+(sy)*(sxw)],factor)
+
+static INLINE void scale_add_line(rgb_group *new,INT32 yn,INT32 newx,
+				  rgb_group *img,INT32 y,INT32 xsize,
+				  double py,double dx)
+{
+   INT32 x,xd;
+   double xn;
+   for (x=0,xn=0; x<THIS->xsize; x++,xn+=dx)
+   {
+      if ((INT32)xn<(INT32)(xn+dx))
+      {
+         scale_add_pixel(new,(INT32)xn,yn,newx,img,x,y,xsize,py*(1.0-decimals(xn)));
+	 if ((xd=(INT32)(xn+dx)-(INT32)(xn))>1) 
+            while (--xd)
+               scale_add_pixel(new,(INT32)xn+xd,yn,newx,img,x,y,xsize,py);
+	 scale_add_pixel(new,(INT32)(xn+dx),yn,newx,img,x,y,xsize,py*decimals(xn+dx));
+      }
+      else
+         scale_add_pixel(new,(int)xn,yn,newx,img,x,y,xsize,py*dx);
+   }
+}
+
+void img_scale(struct image *dest,
+	       struct image *source,
+	       INT32 newx,INT32 newy)
+{
+   rgb_group *new;
+   INT32 y,yd;
+   double yn,dx,dy;
+
+   if (dest->img) { free(dest->img); dest->img=NULL; }
+
+   if (!THIS->img || newx<=0 || newy<=0) return; /* no way */
+   new=malloc(newx*newy*sizeof(rgb_group) +1);
+   if (!new) error("Out of memory!\n");
+
+   MEMSET(new,0,newx*newy*sizeof(rgb_group));
+   
+   dx=((double)newx-0.000001)/source->xsize; 
+   dy=((double)newy-0.000001)/source->ysize; 
+
+   for (y=0,yn=0; y<source->ysize; y++,yn+=dy)
+   {
+      if ((INT32)yn<(INT32)(yn+dy))
+      {
+	 scale_add_line(new,(INT32)(yn),newx,source->img,y,source->xsize,
+			(1.0-decimals(yn)),dx);
+	 if ((yd=(INT32)(yn+dy)-(INT32)(yn))>1) 
+            while (--yd)
+   	       scale_add_line(new,(INT32)yn+yd,newx,source->img,y,source->xsize,
+			      1.0,dx);
+	 scale_add_line(new,(INT32)(yn+dy),newx,source->img,y,source->xsize,
+			(decimals(yn+dy)),dx);
+      }
+      else
+	 scale_add_line(new,(INT32)yn,newx,source->img,y,source->xsize,
+			dy,dx);
+   }
+
+   dest->img=new;
+   dest->xsize=newx;
+   dest->ysize=newy;
+}
+
+/* Special, faster, case for scale=1/2 */
+void img_scale2(struct image *dest, struct image *source)
+{
+   rgb_group *new;
+   INT32 x, y, newx, newy;
+   newx = source->xsize >> 1;
+   newy = source->ysize >> 1;
+   
+   if (dest->img) { free(dest->img); dest->img=NULL; }
+   if (!THIS->img || newx<=0 || newy<=0) return; /* no way */
+   new=malloc(newx*newy*sizeof(rgb_group) +1);
+   if (!new) error("Out of memory\n");
+   MEMSET(new,0,newx*newy*sizeof(rgb_group));
+
+   dest->img=new;
+   dest->xsize=newx;
+   dest->ysize=newy;
+   for (y = 0; y < newy; y++)
+     for (x = 0; x < newx; x++) {
+       pixel(dest,x,y).r = (COLOURTYPE)
+	                    (((INT32) pixel(source,2*x+0,2*y+0).r+
+			      (INT32) pixel(source,2*x+1,2*y+0).r+
+			      (INT32) pixel(source,2*x+0,2*y+1).r+
+			      (INT32) pixel(source,2*x+1,2*y+1).r) >> 2);
+       pixel(dest,x,y).g = (COLOURTYPE)
+	                    (((INT32) pixel(source,2*x+0,2*y+0).g+
+			      (INT32) pixel(source,2*x+1,2*y+0).g+
+			      (INT32) pixel(source,2*x+0,2*y+1).g+
+			      (INT32) pixel(source,2*x+1,2*y+1).g) >> 2);
+       pixel(dest,x,y).b = (COLOURTYPE)
+	                    (((INT32) pixel(source,2*x+0,2*y+0).b+
+			      (INT32) pixel(source,2*x+1,2*y+0).b+
+			      (INT32) pixel(source,2*x+0,2*y+1).b+
+			      (INT32) pixel(source,2*x+1,2*y+1).b) >> 2);
+     }
+}
+
+
+
+void image_scale(INT32 args)
+{
+   float factor;
+   struct object *o;
+   struct image *newimg;
+   
+   o=clone(image_program,0);
+   newimg=(struct image*)(o->storage);
+
+   if (args==1 && sp[-args].type==T_FLOAT) {
+     if (sp[-args].u.float_number == 0.5)
+       img_scale2(newimg,THIS);
+     else
+       img_scale(newimg,THIS,
+		 (INT32)(THIS->xsize*sp[-args].u.float_number),
+		 (INT32)(THIS->ysize*sp[-args].u.float_number));
+   }
+   else if (args>=2 &&
+	    sp[-args].type==T_INT && sp[-args].u.integer==0 &&
+	    sp[1-args].type==T_INT)
+   {
+      factor=((float)sp[1-args].u.integer)/THIS->ysize;
+      img_scale(newimg,THIS,
+		(INT32)(THIS->xsize*factor),
+		sp[1-args].u.integer);
+   }
+   else if (args>=2 &&
+	    sp[1-args].type==T_INT && sp[1-args].u.integer==0 &&
+	    sp[-args].type==T_INT)
+   {
+      factor=((float)sp[-args].u.integer)/THIS->xsize;
+      img_scale(newimg,THIS,
+		sp[-args].u.integer,
+		(INT32)(THIS->ysize*factor));
+   }
+   else if (args>=2 &&
+	    sp[-args].type==T_FLOAT &&
+	    sp[1-args].type==T_FLOAT)
+      img_scale(newimg,THIS,
+		(INT32)(THIS->xsize*sp[-args].u.float_number),
+		(INT32)(THIS->ysize*sp[1-args].u.float_number));
+   else if (args>=2 &&
+	    sp[-args].type==T_INT &&
+	    sp[1-args].type==T_INT)
+      img_scale(newimg,THIS,
+		sp[-args].u.integer,
+		sp[1-args].u.integer);
+   else
+   {
+      free_object(o);
+      error("illegal arguments to image->scale()\n");
+   }
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_ccw(INT32 args)
+{
+   INT32 i,j;
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+
+   pop_n_elems(args);
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+   img=(struct image*)o->storage;
+   *img=*THIS;
+   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
+   {
+      free_object(o);
+      error("Out of memory\n");
+   }
+   img->xsize=THIS->ysize;
+   img->ysize=THIS->xsize;
+   i=THIS->xsize;
+   src=THIS->img+THIS->xsize-1;
+   dest=img->img;
+   while (i--)
+   {
+      j=THIS->ysize;
+      while (j--) *(dest++)=*(src),src+=THIS->xsize;
+      src--;
+      src-=THIS->xsize*THIS->ysize;
+   }
+
+   push_object(o);
+}
+
+static void img_cw(struct image *is,struct image *id)
+{
+   INT32 i,j;
+   rgb_group *src,*dest;
+
+   if (id->img) free(id->img);
+   *id=*is;
+   if (!(id->img=malloc(sizeof(rgb_group)*is->xsize*is->ysize+1)))
+      error("Out of memory\n");
+
+   id->xsize=is->ysize;
+   id->ysize=is->xsize;
+   i=is->xsize;
+   src=is->img+is->xsize-1;
+   dest=id->img;
+   while (i--)
+   {
+      j=is->ysize;
+      while (j--) *(dest++)=*(src),src+=is->xsize;
+      src--;
+      src-=is->xsize*is->ysize;
+   }
+}
+
+void img_ccw(struct image *is,struct image *id)
+{
+   INT32 i,j;
+   rgb_group *src,*dest;
+
+   if (id->img) free(id->img);
+   *id=*is;
+   if (!(id->img=malloc(sizeof(rgb_group)*is->xsize*is->ysize+1)))
+      error("Out of memory\n");
+
+   id->xsize=is->ysize;
+   id->ysize=is->xsize;
+   i=is->xsize;
+   src=is->img+is->xsize-1;
+   dest=id->img+is->xsize*is->ysize;
+   while (i--)
+   {
+      j=is->ysize;
+      while (j--) *(--dest)=*(src),src+=is->xsize;
+      src--;
+      src-=is->xsize*is->ysize;
+   }
+}
+
+void image_cw(INT32 args)
+{
+   INT32 i,j;
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+
+   pop_n_elems(args);
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+   img=(struct image*)o->storage;
+   *img=*THIS;
+   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
+   {
+      free_object(o);
+      error("Out of memory\n");
+   }
+   img->xsize=THIS->ysize;
+   img->ysize=THIS->xsize;
+   i=THIS->xsize;
+   src=THIS->img+THIS->xsize-1;
+   dest=img->img+THIS->xsize*THIS->ysize;
+   while (i--)
+   {
+      j=THIS->ysize;
+      while (j--) *(--dest)=*(src),src+=THIS->xsize;
+      src--;
+      src-=THIS->xsize*THIS->ysize;
+   }
+
+   push_object(o);
+}
+
+void image_mirrorx(INT32 args)
+{
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+   INT32 i,j;
+
+   pop_n_elems(args);
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+   img=(struct image*)o->storage;
+   *img=*THIS;
+   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
+   {
+      free_object(o);
+      error("Out of memory\n");
+   }
+   
+   i=THIS->ysize;
+   src=THIS->img+THIS->xsize-1;
+   dest=img->img;
+   while (i--)
+   {
+      j=THIS->xsize;
+      while (j--) *(dest++)=*(src--);
+      src+=THIS->xsize*2;
+   }
+
+   push_object(o);
+}
+
+void image_mirrory(INT32 args)
+{
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+   INT32 i,j;
+
+   pop_n_elems(args);
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+   img=(struct image*)o->storage;
+   *img=*THIS;
+   if (!(img->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize+1)))
+   {
+      free_object(o);
+      error("Out of memory\n");
+   }
+   
+   i=THIS->ysize;
+   src=THIS->img+THIS->xsize*(THIS->ysize-1);
+   dest=img->img;
+   while (i--)
+   {
+      j=THIS->xsize;
+      while (j--) *(dest++)=*(src++);
+      src-=THIS->xsize*2;
+   }
+
+   push_object(o);
+ 
+}
+
+
+#define ROUND(X) ((unsigned char)((X)+0.5))
+
+static void img_skewx(struct image *src,
+		      struct image *dest,
+		      float diff,
+		      int xpn) /* expand pixel for use with alpha instead */
+{
+   double x0,xmod,xm;
+   INT32 y,x,len;
+   rgb_group *s,*d;
+   rgb_group rgb;
+
+   if (dest->img) free(dest->img);
+   if (diff<0) 
+      dest->xsize=ceil(-diff)+src->xsize,x0=-diff;
+   else 
+      dest->xsize=ceil(diff)+src->xsize,x0=0;
+   dest->ysize=src->ysize;
+   len=src->xsize;
+
+   d=dest->img=malloc(sizeof(rgb_group)*dest->xsize*dest->ysize);
+   if (!d) return;
+   s=src->img;
+   
+   xmod=diff/src->ysize;
+   rgb=dest->rgb;
+
+   y=src->ysize;
+   while (y--)
+   {
+      int j;
+      if (xpn) rgb=*s;
+      for (j=x0; j--;) *(d++)=rgb;
+      if (!(xm=(x0-floor(x0))))
+      {
+	 for (j=len; j--;) *(d++)=*(s++);
+	 j=dest->xsize-x0-len;
+      }
+      else
+      {
+	 float xn=1-xm;
+	 if (xpn)
+	    *d=*s;
+	 else
+	    d->r=ROUND(rgb.r*xm+s->r*xn),
+	    d->g=ROUND(rgb.g*xm+s->g*xn),
+	    d->b=ROUND(rgb.b*xm+s->b*xn);
+	 d++;
+	 for (j=len-1; j--;) 
+	 {
+	    d->r=ROUND(s->r*xm+s[1].r*xn),
+	    d->g=ROUND(s->g*xm+s[1].g*xn),
+	    d->b=ROUND(s->b*xm+s[1].b*xn);
+	    d++;
+	    s++;
+	 }
+	 if (xpn)
+	    *d=*s;
+	 else
+	    d->r=ROUND(rgb.r*xn+s->r*xm),
+	    d->g=ROUND(rgb.g*xn+s->g*xm),
+	    d->b=ROUND(rgb.b*xn+s->b*xm);
+	 d++;
+	 s++;
+	 j=dest->xsize-x0-len;
+      }
+      if (xpn) rgb=s[-1];
+      while (j--) *(d++)=rgb;
+      x0+=xmod;
+   }
+}
+
+static void img_skewy(struct image *src,
+		      struct image *dest,
+		      float diff,
+		      int xpn) /* expand pixel for use with alpha instead */
+{
+   double y0,ymod,ym;
+   INT32 y,x,len,xsz;
+   rgb_group *s,*d;
+   rgb_group rgb;
+
+   if (dest->img) free(dest->img);
+   if (diff<0) 
+      dest->ysize=ceil(-diff)+src->ysize,y0=-diff;
+   else 
+      dest->ysize=ceil(diff)+src->ysize,y0=0;
+   xsz=dest->xsize=src->xsize;
+   len=src->ysize;
+
+   d=dest->img=malloc(sizeof(rgb_group)*dest->ysize*dest->xsize);
+   if (!d) return;
+   s=src->img;
+   
+   ymod=diff/src->xsize;
+   rgb=dest->rgb;
+
+   x=src->xsize;
+   while (x--)
+   {
+      int j;
+      if (xpn) rgb=*s;
+      for (j=y0; j--;) *d=rgb,d+=xsz;
+      if (!(ym=(y0-floor(y0))))
+      {
+	 for (j=len; j--;) *d=*s,d+=xsz,s+=xsz;
+	 j=dest->ysize-y0-len;
+      }
+      else
+      {
+	 float yn=1-ym;
+	 if (xpn)
+	    *d=*s;
+	 else
+	    d->r=ROUND(rgb.r*ym+s->r*yn),
+	    d->g=ROUND(rgb.g*ym+s->g*yn),
+	    d->b=ROUND(rgb.b*ym+s->b*yn);
+	 d+=xsz;
+	 for (j=len-1; j--;) 
+	 {
+	    d->r=ROUND(s->r*ym+s[xsz].r*yn),
+	    d->g=ROUND(s->g*ym+s[xsz].g*yn),
+	    d->b=ROUND(s->b*ym+s[xsz].b*yn);
+	    d+=xsz;
+	    s+=xsz;
+	 }
+	 if (xpn)
+	    *d=*s;
+	 else
+	    d->r=ROUND(rgb.r*yn+s->r*ym),
+	    d->g=ROUND(rgb.g*yn+s->g*ym),
+	    d->b=ROUND(rgb.b*yn+s->b*ym);
+	 d+=xsz;
+	 s+=xsz;
+	 j=dest->ysize-y0-len;
+      }
+      if (xpn) rgb=s[-xsz];
+      while (j--) *d=rgb,d+=xsz;
+      s-=len*xsz-1;
+      d-=dest->ysize*xsz-1;
+      y0+=ymod;
+   }
+}
+
+void image_skewx(INT32 args)
+{
+   float diff;
+   struct object *o;
+
+   if (args<1)
+      error("too few arguments to image->skewx()\n");
+   else if (sp[-args].type==T_FLOAT)
+      diff=THIS->ysize*sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT)
+      diff=sp[-args].u.integer;
+   else
+      error("illegal argument to image->skewx()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+
+   if (!getrgb((struct image*)(o->storage),1,args,"image->skewx()"))
+      ((struct image*)(o->storage))->rgb=THIS->rgb;
+
+   img_skewx(THIS,(struct image*)(o->storage),diff,0);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_skewy(INT32 args)
+{
+   float diff;
+   struct object *o;
+
+   if (args<1)
+      error("too few arguments to image->skewy()\n");
+   else if (sp[-args].type==T_FLOAT)
+      diff=THIS->xsize*sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT)
+      diff=sp[-args].u.integer;
+   else
+      error("illegal argument to image->skewx()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+
+   if (!getrgb((struct image*)(o->storage),1,args,"image->skewy()"))
+      ((struct image*)(o->storage))->rgb=THIS->rgb;
+
+   img_skewy(THIS,(struct image*)(o->storage),diff,0);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_skewx_expand(INT32 args)
+{
+   float diff;
+   struct object *o;
+
+   if (args<1)
+      error("too few arguments to image->skewx()\n");
+   else if (sp[-args].type==T_FLOAT)
+      diff=THIS->ysize*sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT)
+      diff=sp[-args].u.integer;
+   else
+      error("illegal argument to image->skewx()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+
+   if (!getrgb((struct image*)(o->storage),1,args,"image->skewx()"))
+      ((struct image*)(o->storage))->rgb=THIS->rgb;
+
+   img_skewx(THIS,(struct image*)(o->storage),diff,1);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_skewy_expand(INT32 args)
+{
+   float diff;
+   struct object *o;
+
+   if (args<1)
+      error("too few arguments to image->skewy()\n");
+   else if (sp[-args].type==T_FLOAT)
+      diff=THIS->xsize*sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT)
+      diff=sp[-args].u.integer;
+   else
+      error("illegal argument to image->skewx()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   o=clone(image_program,0);
+
+   if (!getrgb((struct image*)(o->storage),1,args,"image->skewy()"))
+      ((struct image*)(o->storage))->rgb=THIS->rgb;
+
+   img_skewy(THIS,(struct image*)(o->storage),diff,1);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+
+
+void img_rotate(INT32 args,int xpn)
+{
+   float angle;
+   struct object *o;
+   struct image *dest,d0,dest2;
+
+   if (args<1)
+      error("too few arguments to image->rotate()\n");
+   else if (sp[-args].type==T_FLOAT)
+      angle=sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT)
+      angle=sp[-args].u.integer;
+   else
+      error("illegal argument to image->rotate()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   dest2.img=d0.img=NULL;
+
+   if (angle<-135) angle-=360*(int)(angle/360);
+   else if (angle>225) angle-=360*(int)(angle/360);
+   if (angle<-45) 
+   { 
+      img_cw(THIS,&dest2); 
+      angle+=90; 
+   }
+   else if (angle>135) 
+   {
+      img_ccw(THIS,&d0); 
+      img_ccw(&d0,&dest2);  
+      angle-=180;
+   }
+   else if (angle>45) 
+   { 
+      img_ccw(THIS,&dest2);  
+      angle-=180; 
+   }
+   else dest2=*THIS;
+   
+   angle=(angle/180.0)*3.141592653589793;
+
+   o=clone(image_program,0);
+
+   dest=(struct image*)(o->storage);
+   if (!getrgb(dest,1,args,"image->rotate()"))
+      (dest)->rgb=THIS->rgb;
+   d0.rgb=dest2.rgb=dest->rgb;
+
+   img_skewy(&dest2,dest,-tan(angle/2)*dest2.xsize,xpn);
+   img_skewx(dest,&d0,sin(angle)*dest->ysize,xpn);
+   img_skewy(&d0,dest,-tan(angle/2)*d0.xsize,xpn);
+
+   if (dest2.img!=THIS->img) free(dest2.img);
+   free(d0.img);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_rotate(INT32 args)
+{
+   img_rotate(args,0);
+}
+
+void image_rotate_expand(INT32 args)
+{
+   img_rotate(args,1);
+}
+
diff --git a/src/modules/image/pnm.c b/src/modules/image/pnm.c
new file mode 100644
index 0000000000000000000000000000000000000000..fc964dc3096824eea7aa341b799b73e5511b5ebe
--- /dev/null
+++ b/src/modules/image/pnm.c
@@ -0,0 +1,170 @@
+#include "global.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "stralloc.h"
+#include "global.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "array.h"
+#include "error.h"
+
+#include "image.h"
+
+#define THIS ((struct image *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+#define pixel(_img,x,y) ((_img)->img[(x)+(y)*(_img)->xsize])
+
+
+static INLINE unsigned char getnext(struct pike_string *s,INT32 *pos)
+{
+   if (*pos>=s->len) return 0;
+   if (s->str[(*pos)]=='#')
+      for (;*pos<s->len && ISSPACE(s->str[*pos]);(*pos)++);
+   return s->str[(*pos)++];
+}
+
+static INLINE void skip_to_eol(struct pike_string *s,INT32 *pos)
+{
+   for (;*pos<s->len && s->str[*pos]!=10;(*pos)++);
+}
+
+static INLINE unsigned char getnext_skip_comment(struct pike_string *s,INT32 *pos)
+{
+   unsigned char c;
+   while ((c=getnext(s,pos))=='#')
+      skip_to_eol(s,pos);
+   return c;
+}
+
+static INLINE void skipwhite(struct pike_string *s,INT32 *pos)
+{
+   while (*pos<s->len && 
+	  ( ISSPACE(s->str[*pos]) ||
+	    s->str[*pos]=='#'))
+      getnext_skip_comment(s,pos);
+}
+
+static INLINE INT32 getnextnum(struct pike_string *s,INT32 *pos)
+{
+   INT32 i;
+   skipwhite(s,pos);
+   i=0;
+   while (*pos<s->len &&
+	  s->str[*pos]>='0' && s->str[*pos]<='9')
+   {
+      i=(i*10)+s->str[*pos]-'0';
+      getnext(s,pos);
+   }
+   return i;
+}
+
+static char* img_frompnm(struct pike_string *s)
+{
+   struct image new;
+   INT32 type,c=0,maxval=255;
+   INT32 pos=0,x,y,i;
+
+   skipwhite(s,&pos);
+   if (getnext(s,&pos)!='P') return "not pnm"; /* not pnm */
+   type=getnext(s,&pos);
+   if (type<'1'||type>'6') return "unknown type"; /* unknown type */
+   new.xsize=getnextnum(s,&pos);
+   new.ysize=getnextnum(s,&pos);
+   if (new.xsize<=0||new.ysize<=0) return "illegal size"; /* illegal size */
+   if (type=='3'||type=='2'||type=='6'||type=='5')
+      maxval=getnextnum(s,&pos);
+   new.img=malloc(new.xsize*new.ysize*sizeof(rgb_group)+1);
+   if (!new.img) error("Out of memory.\n");
+
+   if (type=='1'||type=='2'||type=='3')
+   {
+     skipwhite(s,&pos);
+   }
+   else
+   {
+     skip_to_eol(s,&pos);
+     pos++;
+   }
+   for (y=0; y<new.ysize; y++)
+   {
+      for (i=0,x=0; x<new.xsize; x++)
+      {
+         switch (type)
+	 {
+	    case '1':
+	       c=getnextnum(s,&pos);
+               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
+	          (unsigned char)~(c*255);
+	       break;
+	    case '2':
+	       c=getnextnum(s,&pos);
+               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
+	          (unsigned char)((c*255L)/maxval);
+	       break;
+	    case '3':
+	       pixel(&new,x,y).r=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
+	       pixel(&new,x,y).g=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
+	       pixel(&new,x,y).b=(unsigned char)((getnextnum(s,&pos)*255L)/maxval);
+	       break;
+	    case '4':
+	       if (!i) c=getnext(s,&pos),i=8;
+               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
+	          (unsigned char)~(((c>>7)&1)*255);
+	       c<<=1;
+	       i--;
+	       break;
+	    case '5':
+	       c=getnext(s,&pos);
+               pixel(&new,x,y).r=pixel(&new,x,y).g=pixel(&new,x,y).b=
+	          (unsigned char)((c*255L)/maxval);
+	       break;
+	    case '6':
+	       pixel(&new,x,y).r=(unsigned char)((getnext(s,&pos)*255L)/maxval);
+	       pixel(&new,x,y).g=(unsigned char)((getnext(s,&pos)*255L)/maxval);
+	       pixel(&new,x,y).b=(unsigned char)((getnext(s,&pos)*255L)/maxval);
+	       break;
+	 }
+      }
+   }
+   if (THIS->img) free(THIS->img);
+   THIS->xsize=new.xsize;
+   THIS->ysize=new.ysize;
+   THIS->img=new.img;
+   return NULL;
+}
+
+void image_toppm(INT32 args)
+{
+   char buf[80];
+   struct pike_string *a,*b;
+   
+   pop_n_elems(args);
+   if (!THIS->img) { error("no image\n");  return; }
+   sprintf(buf,"P6\n%d %d\n255\n",THIS->xsize,THIS->ysize);
+   a=make_shared_string(buf);
+   b=make_shared_binary_string((char*)THIS->img,
+			       THIS->xsize*THIS->ysize*3);
+   push_string(add_shared_strings(a,b));
+   free_string(a);
+   free_string(b);
+}
+
+
+void image_frompnm(INT32 args)
+{
+   char *s;
+   if (args<1||
+       sp[-args].type!=T_STRING)
+      error("Illegal argument to image->frompnm()\n");
+   s=img_frompnm(sp[-args].u.string);
+   pop_n_elems(args);
+   if (!s) { push_object(THISOBJ); THISOBJ->refs++; }
+   else push_string(make_shared_string(s));
+}
+
diff --git a/src/modules/image/togif.c b/src/modules/image/togif.c
index f8a15ba5980540e0e35c634cf401c3f970706251..185430022b125004fd0f2bc66d6dff767155915c 100644
--- a/src/modules/image/togif.c
+++ b/src/modules/image/togif.c
@@ -377,3 +377,4 @@ int image_decode_gif(struct image *dest,struct image *dest_alpha,
    if (arena) free(arena);
    return 1; /* ok */
 }
+