diff --git a/.gitattributes b/.gitattributes
index ad0703b1d1dcabd1b91d9afaea1f83aef47c6a01..c2640408f57b98d540e1b84f6d4b63c55e6d2a53 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -22,6 +22,28 @@ testfont binary
 /src/mapping.c foreign_ident
 /src/modules/Gdbm/gdbmmod.c foreign_ident
 /src/modules/Gmp/mpz_glue.c foreign_ident
+/src/modules/Gz/zlibmod.c foreign_ident
+/src/modules/Image/blit.c foreign_ident
+/src/modules/Image/dct.c foreign_ident
+/src/modules/Image/font.c foreign_ident
+/src/modules/Image/image.c foreign_ident
+/src/modules/Image/image.h foreign_ident
+/src/modules/Image/lzw.c foreign_ident
+/src/modules/Image/lzw.h foreign_ident
+/src/modules/Image/matrix.c foreign_ident
+/src/modules/Image/operator.c foreign_ident
+/src/modules/Image/pattern.c foreign_ident
+/src/modules/Image/pnm.c foreign_ident
+/src/modules/Image/quant.c foreign_ident
+/src/modules/Image/togif.c foreign_ident
+/src/modules/Mysql/Makefile.in foreign_ident
+/src/modules/Mysql/acconfig.h foreign_ident
+/src/modules/Mysql/configure.in foreign_ident
+/src/modules/Mysql/mysql.c foreign_ident
+/src/modules/Mysql/precompiled_mysql.h foreign_ident
+/src/modules/Mysql/result.c foreign_ident
+/src/modules/Pipe/pipe.c foreign_ident
+/src/modules/Ssleay/ssleay.c foreign_ident
 /src/modules/_Crypto/cbc.c foreign_ident
 /src/modules/_Crypto/crypto.c foreign_ident
 /src/modules/_Crypto/crypto.h foreign_ident
diff --git a/src/modules/Gz/.cvsignore b/src/modules/Gz/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..b60f1913a1ded943910c93507177d28ab055c55c
--- /dev/null
+++ b/src/modules/Gz/.cvsignore
@@ -0,0 +1,9 @@
+.pure
+Makefile
+config.log
+config.status
+configure
+dependencies
+linker_options
+stamp-h
+zlib_machine.h
diff --git a/src/modules/Gz/.gitignore b/src/modules/Gz/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f4cfce0a1e458466cce958d18029e91a3f73c208
--- /dev/null
+++ b/src/modules/Gz/.gitignore
@@ -0,0 +1,9 @@
+/.pure
+/Makefile
+/config.log
+/config.status
+/configure
+/dependencies
+/linker_options
+/stamp-h
+/zlib_machine.h
diff --git a/src/modules/Gz/Makefile.in b/src/modules/Gz/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..5b2722ba6fdbe3f505d116d289f2140a1e6ecd5b
--- /dev/null
+++ b/src/modules/Gz/Makefile.in
@@ -0,0 +1,7 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+OBJS=zlibmod.o
+MODULE_LDFLAGS=@LIBS@
+
+@dynamic_module_makefile@
+@dependencies@
diff --git a/src/modules/Gz/configure.in b/src/modules/Gz/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..4576722861dd7696625c27fd835ae06a51a20b63
--- /dev/null
+++ b/src/modules/Gz/configure.in
@@ -0,0 +1,17 @@
+AC_INIT(zlibmod.c)
+AC_CONFIG_HEADER(zlib_machine.h)
+AC_ARG_WITH(zlib,     [  --with(out)-zlib       Support gzip compression],[],[with_zlib=yes])
+
+sinclude(../module_configure.in)
+
+if test x$with_zlib = xyes ; then
+  AC_CHECK_HEADERS(zlib.h)
+  if test $ac_cv_header_zlib_h = yes ; then
+    AC_CHECK_LIB(z, compress, [[LIBS="${LIBS-} -lz"]],
+      AC_CHECK_LIB(gz, compress))
+  fi
+fi
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
+
+
diff --git a/src/modules/Gz/doc/gz_deflate b/src/modules/Gz/doc/gz_deflate
new file mode 100644
index 0000000000000000000000000000000000000000..7873bfcf42f182718c72942e53c2ca53e6f0fdf4
--- /dev/null
+++ b/src/modules/Gz/doc/gz_deflate
@@ -0,0 +1,61 @@
+NAME
+	Gz_deflate - gzip packer
+
+DESCRIPTION
+	Gz_inflate is a builtin program written in C. It interfaces the
+	packing routines in the libz library.
+
+NOTA BENE
+	This program is only available if libz was available and found when
+	Pike was compiled.
+
+SEE ALSO
+	gz_inflate
+
+============================================================================
+NAME
+	create - initialize gzip packer
+
+SYNTAX
+	void create(int X)
+	or
+	object(Gz_deflate) Gz_deflate(int X)
+
+DESCRIPTION
+	This functionion is called when a new Gz_deflate is created.
+	If given, X should be a number from 0 to 9 indicating the packing /
+	cpu ratio. Zero means no packing, 2-3 is considered 'fast', 6 is
+	default and higher is considered 'slow' but gives better packing.
+
+	This function can also be used to re-initialize a gz_deflate object
+	so it can be re-used.
+
+============================================================================
+NAME
+	deflate - pack data
+
+SYNTAX
+	string deflate(string data, int flush);
+
+DESCRIPTION
+	This function preforms gzip style compression on a string and
+	returns the packed data. Streaming can be done by calling this
+	functon several time and concatenating the returned data.
+	The optional 'flush' argument should be one f the following:
+
+	Gz_deflate->NO_FLUSH      	Only data that doesn't fit in the
+	                           	internal buffers is returned.
+	Gz_deflate->PARTIAL_FLUSH	All input is packed and returned.
+	Gz_deflate->SYNC_FLUSH   	All input is packed and returned.
+	                        	Packing is syncronized.
+	Gz_deflate->FINISH       	All input is packed and an 'end of
+					data' marker is appended.
+
+	Using flushing will degrade packing. Normally NO_FLUSH should be
+	used until the end of the data when FINISH should be used. For
+	interactive data PARTIAL_FLUSH should be used.
+
+SEE ALSO
+	gz_inflate->inflate
+
+============================================================================
diff --git a/src/modules/Gz/doc/gz_inflate b/src/modules/Gz/doc/gz_inflate
new file mode 100644
index 0000000000000000000000000000000000000000..153f1727d56c5459a27e69463cb6defaaba13ef1
--- /dev/null
+++ b/src/modules/Gz/doc/gz_inflate
@@ -0,0 +1,53 @@
+NAME
+	Gz_inflate - gzip unpacker
+
+DESCRIPTION
+	Gz_inflate is a builtin program written in C. It interfaces the
+	packing routines in the libz library.
+
+NOTA BENE
+	This program is only available if libz was available and found when
+	Pike was compiled.
+
+SEE ALSO
+	gz_deflate
+
+============================================================================
+NAME
+	create - initialize gzip packer
+
+SYNTAX
+	void create()
+	or
+	object(Gz_inflate) Gz_inflate()
+
+DESCRIPTION
+	This functionion is called when a new Gz_inflate is created.
+	It can also be called after the object has been used to re-initialize
+	it.
+
+============================================================================
+NAME
+	inflate - unpack data
+
+SYNTAX
+	string inflate(string data);
+
+DESCRIPTION
+	This function preforms gzip style decompression. It can inflate
+	a whole file at once or in blocks.
+
+EXAMPLES
+	#include <stdio.h>
+	// whole file
+	write(Gz_inflate()->inflate(stdin->read(0x7fffffff));
+
+	// streaming (blocks)
+	function inflate=Gz_inflate()->inflate;
+	while(string s=stdin->read(8192))
+	  write(inflate(s));
+
+SEE ALSO
+	gz_deflate->deflate
+
+============================================================================
diff --git a/src/modules/Gz/testsuite.in b/src/modules/Gz/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..fdc03eb7f782e65352f30754796b29a963041b48
--- /dev/null
+++ b/src/modules/Gz/testsuite.in
@@ -0,0 +1,17 @@
+cond([[ master()->programs["/precompiled/gz_inflate"] ]],
+[[
+  test_true(Gz_deflate())
+  test_true(Gz_deflate()->deflate)
+  test_true(Gz_inflate())
+  test_true(Gz_inflate()->inflate)
+define(dotest,[[
+  test_true(Gz_deflate(1)->deflate($1))
+  test_eq(Gz_inflate()->inflate(Gz_deflate(1)->deflate($1)),$1)
+  test_eq(Gz_inflate()->inflate(Gz_deflate(9)->deflate($1)),$1)
+  test_any([[object o=Gz_deflate(); return Gz_inflate()->inflate(o->deflate($1,o->PARTIAL_FLUSH) + o->deflate($1,o->FINISH))]], [[($1)+($1)]])
+]])
+  dotest("")
+  dotest("foo")
+  dotest(sprintf("%'fomp'1000n"))
+  dotest(sprintf("%'fomp'100000n"))
+]])
diff --git a/src/modules/Gz/zlib_machine.h.in b/src/modules/Gz/zlib_machine.h.in
new file mode 100644
index 0000000000000000000000000000000000000000..7751a13fcd8c43413bf8bea820c72dfc7e0659ea
--- /dev/null
+++ b/src/modules/Gz/zlib_machine.h.in
@@ -0,0 +1,13 @@
+#ifndef GMP_MACHINE_H
+#define GMP_MACHINE_H
+
+/* Define this if you have <libz.h> */
+#undef HAVE_ZLIB_H
+
+/* Define this if you have -lz */
+#undef HAVE_LIBZ
+
+/* Define this if you have -lgz */
+#undef HAVE_LIBGZ
+
+#endif
diff --git a/src/modules/Gz/zlibmod.c b/src/modules/Gz/zlibmod.c
new file mode 100644
index 0000000000000000000000000000000000000000..d293e1f0f84f4bce51e525667c61838de93ade96
--- /dev/null
+++ b/src/modules/Gz/zlibmod.c
@@ -0,0 +1,373 @@
+/*\
+||| This file a part of Pike, and is copyright by Fredrik Hubinette
+||| Pike is distributed as GPL (General Public License)
+||| See the files COPYING and DISCLAIMER for more information.
+\*/
+#include "global.h"
+RCSID("$Id: zlibmod.c,v 1.1 1997/02/11 08:34:52 hubbe Exp $");
+
+#include "zlib_machine.h"
+#include "types.h"
+
+#if !defined(HAVE_LIBZ) && !defined(HAVE_LIBGZ)
+#undef HAVE_ZLIB_H
+#endif
+
+#ifdef HAVE_ZLIB_H
+
+#include "interpret.h"
+#include "svalue.h"
+#include "stralloc.h"
+#include "array.h"
+#include "macros.h"
+#include "program.h"
+#include "stralloc.h"
+#include "object.h"
+#include "pike_types.h"
+#include "threads.h"
+#include "dynamic_buffer.h"
+
+#include <zlib.h>
+
+struct zipper
+{
+  struct z_stream_s gz;
+  DEFINE_MUTEX(lock);
+};
+
+#define BUF 16384
+
+#define THIS ((struct zipper *)(fp->current_storage))
+
+static void gz_deflate_create(INT32 args)
+{
+  int level=Z_DEFAULT_COMPRESSION;
+
+  if(THIS->gz.state)
+  {
+    mt_lock(& this->lock);
+    deflateEnd(&THIS->gz);
+    mt_unlock(& this->lock);
+  }
+
+  if(args)
+  {
+    if(sp[-args].type != T_INT)
+      error("Bad argument 1 to gz->create()\n");
+    if(sp[-args].u.integer < Z_NO_COMPRESSION ||
+       sp[-args].u.integer > Z_BEST_COMPRESSION)
+    {
+      error("Compression level out of range for gz_deflate->create()\n");
+    }
+  }
+
+  THIS->gz.zalloc=Z_NULL;
+  THIS->gz.zfree=Z_NULL;
+  THIS->gz.opaque=THIS;
+
+  pop_n_elems(args);
+  mt_lock(& THIS->lock);
+  level=deflateInit(&THIS->gz, level);
+  mt_unlock(& THIS->lock);
+  switch(level)
+  {
+  case Z_OK:
+    return;
+
+  case Z_VERSION_ERROR:
+    error("libz not compatible with zlib.h!!!\n");
+    break;
+
+  default:
+    if(THIS->gz.msg)
+      error("Failed to initialize gz_deflate: %s\n",THIS->gz.msg);
+    else
+      error("Failed to initialize gz_deflate\n");
+  }
+}
+
+static int do_deflate(dynamic_buffer *buf,
+		      struct zipper *this,
+		      int flush)
+{
+  int fail=0;
+
+  THREADS_ALLOW();
+  mt_lock(& this->lock);
+  if(!this->gz.state)
+  {
+    fail=Z_STREAM_ERROR;
+  }else{
+    do
+      {
+	char *loc;
+	int ret;
+	loc=low_make_buf_space(BUF,buf);
+	this->gz.next_out=(Bytef *)loc;
+	this->gz.avail_out=BUF;
+	ret=deflate(& this->gz, flush);
+	low_make_buf_space(-this->gz.avail_out,buf);
+	if(ret != Z_OK)
+	  {
+	    fail=ret;
+	    break;
+	  }
+      } while(!this->gz.avail_out || flush==Z_FINISH || this->gz.avail_in);
+  }
+
+  mt_unlock(& this->lock);
+  THREADS_DISALLOW();
+  return fail;
+}
+
+static void gz_deflate(INT32 args)
+{
+  struct pike_string *data;
+  int flush, fail;
+  struct zipper *this=THIS;
+  dynamic_buffer buf;
+
+  if(!THIS->gz.state)
+    error("gz_deflate not initialized or destructed\n");
+
+  initialize_buf(&buf);
+
+  if(args<1)
+    error("Too few arguments to gz_deflate->deflate()\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to gz_deflate->deflate()\n");
+
+  data=sp[-args].u.string;
+
+  if(args>1)
+  {
+    if(sp[1-args].type != T_INT)
+      error("Bad argument 2 to gz_deflate->deflate()\n");
+    
+    flush=sp[1-args].u.integer;
+
+    switch(flush)
+    {
+    case Z_PARTIAL_FLUSH:
+    case Z_FINISH:
+    case Z_SYNC_FLUSH:
+    case Z_NO_FLUSH:
+      break;
+
+    defualt:
+      error("Argument 2 to gz_deflate->deflate() out of range.\n");
+    }
+  }else{
+    flush=Z_FINISH;
+  }
+
+  this->gz.next_in=(Bytef *)data->str;
+  this->gz.avail_in=data->len;
+
+  fail=do_deflate(&buf,this,flush);
+  pop_n_elems(args);
+
+  if(fail != Z_OK && fail != Z_STREAM_END)
+  {
+    free(buf.s.str);
+    if(THIS->gz.msg)
+      error("Error in gz_deflate->deflate(): %s\n",THIS->gz.msg);
+    else
+      error("Error in gz_deflate->deflate(): %d\n",fail);
+  }
+
+  push_string(low_free_buf(&buf));
+}
+
+
+static void init_gz_deflate(struct object *o)
+{
+  mt_init(& THIS->locked);
+  MEMSET(& THIS->gz, 0, sizeof(THIS->gz));
+  THIS->gz.zalloc=Z_NULL;
+  THIS->gz.zfree=Z_NULL;
+  THIS->gz.opaque=THIS;
+  deflateInit(& THIS->gz, Z_DEFAULT_COMPRESSION);
+}
+
+static void exit_gz_deflate(struct object *o)
+{
+  mt_lock(& THIS->lock);
+  deflateEnd(&THIS->gz);
+  mt_unlock(& THIS->lock);
+}
+
+/*******************************************************************/
+
+
+static void gz_inflate_create(INT32 args)
+{
+  int tmp;
+  if(THIS->gz.state)
+  {
+    mt_lock(this->lock);
+    inflateEnd(&THIS->gz);
+    mt_unlock(this->lock);
+  }
+
+
+  THIS->gz.zalloc=Z_NULL;
+  THIS->gz.zfree=Z_NULL;
+  THIS->gz.opaque=THIS;
+
+  pop_n_elems(args);
+  mt_lock(THIS->lock);
+  tmp=inflateInit(& THIS->gz);
+  mt_unlock(THIS->lock);
+  switch(tmp)
+  {
+  case Z_OK:
+    return;
+
+  case Z_VERSION_ERROR:
+    error("libz not compatible with zlib.h!!!\n");
+    break;
+
+  default:
+    if(THIS->gz.msg)
+      error("Failed to initialize gz_inflate: %s\n",THIS->gz.msg);
+    else
+      error("Failed to initialize gz_inflate\n");
+  }
+}
+
+static int do_inflate(dynamic_buffer *buf,
+		      struct zipper *this,
+		      int flush)
+{
+  int fail=0;
+
+  THREADS_ALLOW();
+  mt_lock(this->lock);
+  if(!this->gz.state)
+  {
+    fail=Z_STREAM_ERROR;
+  }else{
+    do
+    {
+      char *loc;
+      int ret;
+      loc=low_make_buf_space(BUF,buf);
+      this->gz.next_out=(Bytef *)loc;
+      this->gz.avail_out=BUF;
+      ret=inflate(& this->gz, flush);
+      low_make_buf_space(-this->gz.avail_out,buf);
+      if(ret != Z_OK)
+	{
+	  fail=ret;
+	  break;
+	}
+    } while(!this->gz.avail_out || flush==Z_FINISH || this->gz.avail_in);
+  }
+  mt_unlock(this->lock);
+  THREADS_DISALLOW();
+  return fail;
+}
+
+static void gz_inflate(INT32 args)
+{
+  struct pike_string *data;
+  int fail;
+  struct zipper *this=THIS;
+  dynamic_buffer buf;
+
+  if(!THIS->gz.state)
+    error("gz_inflate not initialized or destructed\n");
+
+  initialize_buf(&buf);
+
+  if(args<1)
+    error("Too few arguments to gz_inflate->inflate()\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to gz_inflate->inflate()\n");
+
+  data=sp[-args].u.string;
+
+  this->gz.next_in=(Bytef *)data->str;
+  this->gz.avail_in=data->len;
+
+  fail=do_inflate(&buf,this,Z_PARTIAL_FLUSH);
+  pop_n_elems(args);
+
+  if(fail != Z_OK && fail != Z_STREAM_END)
+  {
+    free(buf.s.str);
+    if(THIS->gz.msg)
+      error("Error in gz_inflate->inflate(): %s\n",THIS->gz.msg);
+    else
+      error("Error in gz_inflate->inflate(): %d\n",fail);
+  }
+  push_string(low_free_buf(&buf));
+  if(fail != Z_STREAM_END && fail!=Z_OK && !sp[-1].u.string->len)
+  {
+    pop_stack();
+    push_int(0);
+  }
+}
+
+static void init_gz_inflate(struct object *o)
+{
+  mt_init(THIS->locked);
+  MEMSET(& THIS->gz, 0, sizeof(THIS->gz));
+  THIS->gz.zalloc=Z_NULL;
+  THIS->gz.zfree=Z_NULL;
+  THIS->gz.opaque=0;
+  inflateInit(&THIS->gz);
+  inflateEnd(&THIS->gz);
+}
+
+static void exit_gz_inflate(struct object *o)
+{
+  mt_lock(& THIS->lock);
+  inflateEnd(& THIS->gz);
+  mt_unlock(& THIS->lock);
+}
+
+#endif
+
+void pike_module_exit(void) {}
+
+void pike_module_init(void)
+{
+#ifdef HAVE_ZLIB_H
+  start_new_program();
+  add_storage(sizeof(struct zipper));
+  
+  add_function("create",gz_deflate_create,"function(int|void:void)",0);
+  add_function("deflate",gz_deflate,"function(string,int|void:string)",0);
+
+  add_integer_constant("NO_FLUSH",Z_NO_FLUSH,0);
+  add_integer_constant("PARTIAL_FLUSH",Z_PARTIAL_FLUSH,0);
+  add_integer_constant("SYNC_FLUSH",Z_SYNC_FLUSH,0);
+  add_integer_constant("FINISH",Z_FINISH,0);
+
+  set_init_callback(init_gz_deflate);
+  set_exit_callback(exit_gz_deflate);
+
+  end_class("deflate",0);
+
+  start_new_program();
+  add_storage(sizeof(struct zipper));
+  
+  add_function("create",gz_inflate_create,"function(int|void:void)",0);
+  add_function("inflate",gz_inflate,"function(string:string)",0);
+
+  add_integer_constant("NO_FLUSH",Z_NO_FLUSH,0);
+  add_integer_constant("PARTIAL_FLUSH",Z_PARTIAL_FLUSH,0);
+  add_integer_constant("SYNC_FLUSH",Z_SYNC_FLUSH,0);
+  add_integer_constant("FINISH",Z_FINISH,0);
+
+  set_init_callback(init_gz_inflate);
+  set_exit_callback(exit_gz_inflate);
+
+  end_class("inflate",0);
+#endif
+}
+
diff --git a/src/modules/Image/.cvsignore b/src/modules/Image/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..47c84b07459ebcb78e36ee8c2e1a30cee7f440ff
--- /dev/null
+++ b/src/modules/Image/.cvsignore
@@ -0,0 +1,8 @@
+.pure
+Makefile
+config.h
+config.log
+config.status
+configure
+dependencies
+stamp-h
diff --git a/src/modules/Image/.gitignore b/src/modules/Image/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b27587e1b501d0baa775ec479e1a69e7da31aef0
--- /dev/null
+++ b/src/modules/Image/.gitignore
@@ -0,0 +1,8 @@
+/.pure
+/Makefile
+/config.h
+/config.log
+/config.status
+/configure
+/dependencies
+/stamp-h
diff --git a/src/modules/Image/Makefile.in b/src/modules/Image/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..b28ff0d6bfbe5515768107fb5d5e380598a2ac1b
--- /dev/null
+++ b/src/modules/Image/Makefile.in
@@ -0,0 +1,16 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+OBJS = image.o font.o quant.o lzw.o togif.o matrix.o pnm.o blit.o \
+	pattern.o dct.o operator.o
+MODNAME=image
+
+@dynamic_module_makefile@
+
+pike: all
+	cd ../..; make
+
+pure: all
+	cd ../..; make pure
+
+
+@dependencies@
diff --git a/src/modules/Image/blit.c b/src/modules/Image/blit.c
new file mode 100644
index 0000000000000000000000000000000000000000..69b056dd3197272dfbf9f5746119965ac0d99e8a
--- /dev/null
+++ b/src/modules/Image/blit.c
@@ -0,0 +1,465 @@
+/* $Id: blit.c,v 1.1 1997/02/11 08:35:41 hubbe Exp $ */
+#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 "threads.h"
+
+#include "image.h"
+
+extern 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))
+
+#if 0
+#include <sys/resource.h>
+#define CHRONO(X) chrono(X)
+
+static void chrono(char *x)
+{
+   struct rusage r;
+   static struct rusage rold;
+   getrusage(RUSAGE_SELF,&r);
+   fprintf(stderr,"%s: %ld.%06ld - %ld.%06ld\n",x,
+	   r.ru_utime.tv_sec,r.ru_utime.tv_usec,
+
+	   ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?-1:0)
+	   +r.ru_utime.tv_sec-rold.ru_utime.tv_sec,
+           ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?1000000:0)
+           + r.ru_utime.tv_usec-rold.ru_utime.tv_usec
+	   );
+
+   rold=r;
+}
+#else
+#define CHRONO(X)
+#endif
+
+/***************** internals ***********************************/
+
+#define testrange(x) max(min((x),255),0)
+
+#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)
+{
+   THREADS_ALLOW();
+   while (size--) *(dest++)=rgb;
+   THREADS_DISALLOW();
+}
+
+void img_box_nocheck(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
+{
+   INT32 x,mod;
+   rgb_group *foo,*end,rgb;
+   struct image *this;
+
+   this=THIS;
+   
+   THREADS_ALLOW();
+   mod=this->xsize-(x2-x1)-1;
+   foo=this->img+x1+y1*this->xsize;
+   end=this->img+x1+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);
+   THREADS_DISALLOW();
+}
+
+
+void img_blit(rgb_group *dest,rgb_group *src,INT32 width,
+	      INT32 lines,INT32 moddest,INT32 modsrc)
+{
+CHRONO("image_blit begin");
+
+   THREADS_ALLOW();
+   while (lines--)
+   {
+      MEMCPY(dest,src,sizeof(rgb_group)*width);
+      dest+=moddest;
+      src+=modsrc;
+   }
+   THREADS_DISALLOW();
+CHRONO("image_blit end");
+
+}
+
+void img_crop(struct image *dest,
+	      struct image *img,
+	      INT32 x1,INT32 y1,
+	      INT32 x2,INT32 y2)
+{
+   rgb_group *new;
+   INT32 xp,yp,xs,ys;
+
+   if (dest->img) { free(dest->img); dest->img=NULL; }
+
+   if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
+   if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
+
+   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");
+      THREADS_ALLOW();
+      MEMCPY(new,img->img,(x2-x1+1)*(y2-y1+1)*sizeof(rgb_group));
+      THREADS_DISALLOW();
+      dest->img=new;
+      return;
+   }
+
+   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));
+
+   dest->xsize=x2-x1+1;
+   dest->ysize=y2-y1+1;
+
+   xp=max(0,-x1);
+   yp=max(0,-y1);
+   xs=max(0,x1);
+   ys=max(0,y1);
+
+   if (x1<0) x1=0; else if (x1>=img->xsize) x1=img->xsize-1;
+   if (y1<0) y1=0; else if (y1>=img->ysize) y1=img->ysize-1;
+   if (x2<0) x2=0; else if (x2>=img->xsize) x2=img->xsize-1;
+   if (y2<0) y2=0; else if (y2>=img->ysize) y2=img->ysize-1;
+
+   img_blit(new+xp+yp*dest->xsize,
+	    img->img+xs+(img->xsize)*ys,
+	    x2-x1+1,
+	    y2-y1+1,
+	    dest->xsize,
+	    img->xsize);
+
+   dest->img=new;
+}
+
+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");
+   THREADS_ALLOW();
+   MEMCPY(newimg->img,img->img,sizeof(rgb_group)*img->xsize*img->ysize);
+   THREADS_DISALLOW();
+   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;
+
+   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);
+
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+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;
+
+/* tr�da h�r n�ndag */
+
+   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,smod,dmod,mmod;
+   rgb_group *s,*d,*m;
+   float q;
+
+CHRONO("image_paste_mask init");
+
+   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));
+
+CHRONO("image_paste_mask begin");
+
+   s=img->img+max(0,-x1)+max(0,-y1)*img->xsize;
+   m=mask->img+max(0,-x1)+max(0,-y1)*mask->xsize;
+   d=THIS->img+max(0,-x1)+x1+(y1+max(0,-y1))*THIS->xsize;
+   x=max(0,-x1);
+   smod=img->xsize-(x2-x);
+   mmod=mask->xsize-(x2-x);
+   dmod=THIS->xsize-(x2-x);
+
+   q=1.0/255;
+
+   THREADS_ALLOW();
+   for (y=max(0,-y1); y<y2; y++)
+   {
+      for (x=max(0,-x1); x<x2; x++)
+      {
+	 if (m->r==255) d->r=s->r;
+	 else if (m->r==0) d->r=d->r;
+	 else d->r=(unsigned char)(((d->r*(255-m->r))+(s->r*m->r))*q);
+	 if (m->g==255) d->g=s->g;
+	 else if (m->g==0) d->g=d->g;
+	 else d->g=(unsigned char)(((d->g*(255-m->g))+(s->g*m->g))*q);
+	 if (m->b==255) d->b=s->b;
+	 else if (m->b==0) d->b=d->b;
+	 else d->b=(unsigned char)(((d->b*(255-m->b))+(s->b*m->b))*q);
+	 s++; m++; d++;
+      }
+      s+=smod; m+=mmod; d+=dmod;
+   }
+   THREADS_DISALLOW();
+CHRONO("image_paste_mask end");
+
+   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;
+   rgb_group rgb,*d,*m;
+   INT32 mmod,dmod;
+   float q;
+
+   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);
+
+CHRONO("image_paste_alpha_color begin");
+
+   m=mask->img+max(0,-x1)+max(0,-y1)*mask->xsize;
+   d=THIS->img+max(0,-x1)+x1+(y1+max(0,-y1))*THIS->xsize;
+   x=max(0,-x1);
+   mmod=mask->xsize-(x2-x);
+   dmod=THIS->xsize-(x2-x);
+
+   q=1.0/255;
+
+   rgb=THIS->rgb;
+
+   THREADS_ALLOW();
+   for (y=max(0,-y1); y<y2; y++)
+   {
+      for (x=max(0,-x1); x<x2; x++)
+      {
+	 if (m->r==255) d->r=rgb.r;
+	 else if (m->r==0) ;
+	 else d->r=(unsigned char)(((d->r*(255-m->r))+(rgb.r*m->r))*q);
+	 if (m->g==255) d->g=rgb.g;
+	 else if (m->g==0) ;
+	 else d->g=(unsigned char)(((d->g*(255-m->g))+(rgb.g*m->g))*q);
+	 if (m->b==255) d->b=rgb.b;
+	 else if (m->b==0) ;
+	 else d->b=(unsigned char)(((d->b*(255-m->b))+(rgb.b*m->b))*q);
+	 m++; d++;
+      }
+      m+=mmod; d+=dmod;
+   }
+   THREADS_DISALLOW();
+CHRONO("image_paste_alpha_color end");
+
+   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/config.h.in b/src/modules/Image/config.h.in
new file mode 100644
index 0000000000000000000000000000000000000000..573e31d4ca89134b34b9caaabc21aaf9f392fcad
--- /dev/null
+++ b/src/modules/Image/config.h.in
@@ -0,0 +1,19 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define if you have a working `mmap' system call.  */
+#undef HAVE_MMAP
+
+/* Define if you have the getpagesize function.  */
+#undef HAVE_GETPAGESIZE
+
+/* Define if you have the <fcntl.h> header file.  */
+#undef HAVE_FCNTL_H
+
+/* Define if you have the <stdlib.h> header file.  */
+#undef HAVE_STDLIB_H
+
+/* Define if you have the <sys/fcntl.h> header file.  */
+#undef HAVE_SYS_FCNTL_H
+
+/* Define if you have the <unistd.h> header file.  */
+#undef HAVE_UNISTD_H
diff --git a/src/modules/Image/configure.in b/src/modules/Image/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..4df99cce8ce80f712f8a1b3dc45eefb9111abc27
--- /dev/null
+++ b/src/modules/Image/configure.in
@@ -0,0 +1,14 @@
+AC_INIT(image.c)
+AC_CONFIG_HEADER(config.h)
+
+sinclude(../module_configure.in)
+
+AC_FUNC_MMAP
+
+AC_CHECK_HEADERS(sys/fcntl.h fcntl.h stdlib.h)
+
+AC_SUBST(RANLIB)
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
+
+
diff --git a/src/modules/Image/dct.c b/src/modules/Image/dct.c
new file mode 100644
index 0000000000000000000000000000000000000000..7bd39bd4b4049c53a5efbe54b858c2cb819242ae
--- /dev/null
+++ b/src/modules/Image/dct.c
@@ -0,0 +1,165 @@
+/* $Id: dct.c,v 1.1 1997/02/11 08:35:42 hubbe Exp $ */
+
+#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"
+
+extern 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))
+#define testrange(x) max(min((x),255),0)
+
+static const double c0=0.70710678118654752440;
+static const double pi=3.14159265358979323846;
+
+void image_dct(INT32 args)
+{
+   rgbd_group *area,*val;
+   struct object *o;
+   struct image *img;
+   INT32 x,y,u,v;
+   double xsz2,ysz2,enh,xp,yp,dx,dy;
+   double *costbl;
+   rgb_group *pix;
+   
+   if (!THIS->img) error("no image\n");
+
+   fprintf(stderr,"%d bytes, %d bytes\n",
+	   sizeof(rgbd_group)*THIS->xsize*THIS->ysize,
+	   sizeof(rgb_group)*THIS->xsize*THIS->ysize+1);
+    
+   if (!(area=malloc(sizeof(rgbd_group)*THIS->xsize*THIS->ysize+1)))
+      error("Out of memory\n");
+
+   if (!(costbl=malloc(sizeof(double)*THIS->xsize+1)))
+   {
+      free(area);
+      error("Out of memory\n");
+   }
+
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+   *img=*THIS;
+   
+   if (args>=2 
+       && sp[-args].type==T_INT 
+       && sp[1-args].type==T_INT)
+   {
+      img->xsize=max(1,sp[-args].u.integer);
+      img->ysize=max(1,sp[1-args].u.integer);
+   }
+   else error("Illegal arguments to image->dct()\n");
+
+   if (!(img->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize+1)))
+   {
+      free(area);
+      free(costbl);
+      free_object(o);
+      error("Out of memory\n");
+   }
+
+   xsz2=THIS->xsize*2.0;
+   ysz2=THIS->ysize*2.0;
+
+   enh=(8.0/THIS->xsize)*(8.0/THIS->ysize);
+
+   for (u=0; u<THIS->xsize; u++)
+   {
+      double d,z0;
+      rgbd_group sum;
+
+      for (v=0; v<THIS->ysize; v++)
+      {
+	 d=(u?1:c0)*(v?1:c0)/4.0;
+	 sum.r=sum.g=sum.b=0;
+	 pix=THIS->img;
+	 
+	 for (x=0; x<THIS->xsize; x++)
+	    costbl[x]=cos( (2*x+1)*u*pi/xsz2 );
+
+	 for (y=0; y<THIS->ysize; y++)
+	 {
+	    z0=cos( (2*y+1)*v*pi/ysz2 );
+	    for (x=0; x<THIS->xsize; x++)
+	    {
+	       double z;
+	       z =  costbl[x] * z0;
+	       sum.r+=pix->r*z;
+	       sum.g+=pix->g*z;
+	       sum.b+=pix->b*z;
+	       pix++;
+	    }
+	 }
+	 sum.r*=d;
+	 sum.g*=d;
+	 sum.b*=d;
+	 area[u+v*THIS->xsize]=sum;
+      }
+      fprintf(stderr,"."); fflush(stderr);
+   }
+   fprintf(stderr,"\n");
+
+   dx=((double)(THIS->xsize-1))/(img->xsize);
+   dy=((double)(THIS->ysize-1))/(img->ysize);
+
+   pix=img->img;
+   for (y=0,yp=0; y<img->ysize; y++,yp+=dy)
+   {
+      double z0;
+      rgbd_group sum;
+
+      for (x=0,xp=0; x<img->xsize; x++,xp+=dx)
+      {
+	 sum.r=sum.g=sum.b=0;
+	 val=area;
+
+	 for (u=0; u<THIS->xsize; u++)
+	    costbl[u]=cos( (2*xp+1)*u*pi/xsz2 );
+
+	 for (v=0; v<THIS->ysize; v++)
+	 {
+	    z0=cos( (2*yp+1)*v*pi/ysz2 )*(v?1:c0)/4.0;
+	    for (u=0; u<THIS->xsize; u++)
+	    {
+	       double z;
+	       z = (u?1:c0) * costbl[u] * z0; 
+	       sum.r+=val->r*z;
+	       sum.g+=val->g*z;
+	       sum.b+=val->b*z;
+	       val++;
+	    }
+	 }
+	 sum.r*=enh;
+	 sum.g*=enh;
+	 sum.b*=enh;
+	 pix->r=testrange(((int)(sum.r+0.5)));
+	 pix->g=testrange(((int)(sum.g+0.5)));
+	 pix->b=testrange(((int)(sum.b+0.5)));
+	 pix++;
+      }
+      fprintf(stderr,"."); fflush(stderr);
+   }
+
+   free(area);
+   free(costbl);
+
+   pop_n_elems(args);
+   push_object(o);
+}
diff --git a/src/modules/Image/doc.txt b/src/modules/Image/doc.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3f3cc2b2c97015454379ad1abd3bfff16f6abec5
--- /dev/null
+++ b/src/modules/Image/doc.txt
@@ -0,0 +1,487 @@
+                                Pike module:
+
+                                    image
+
+                       Pontus Hagland law@infovav.se
+                         Per Hedbor per@infovav.se
+                        David K�gedal kg@infovav.se
+----------------------------------------------------------------------------
+This package adds two Pike progams:
+
+   * "precompiled/image" and
+   * "precompiled/font".
+
+----------------------------------------------------------------------------
+
+methods in precompiled/image:
+
+Methods resulting in a new object:
+object clone( [int xsize,int ysize [,int r,int g,int b] ] );
+
+object copy( [int x1,int y1,int x2,int y2 [,int r,int g,int b] ] );
+object autocrop( [int border_width [,int left,int right,int top,int bottom]
+[,int r,int g,int b] ] );
+
+object gray();
+object color(int r,int g,int b);
+object invert();
+
+object mirrorx(void);
+object mirrory(void);
+object rotate_cw(void);
+object rotate_ccw(void);
+object threshold([int r,int g,int b]);
+object apply_matrix(array(array(int)) matrix,[int r,int g,int b[,int div]]);
+
+object scale(float factor);
+object scale(float factorx,float factory);
+object scale(int newx|0,int newy|0);
+
+Methods operating on current object:
+string toppm(void);
+string|object fromppm(string s);
+string togif( [int r,inr g,int b] );
+
+object paste(object img [,int x,int y])
+object paste_alpha(object img, int alpha [,int x, int y]);
+object paste_mask(object img, object alpha_mask [,int x,int y]);
+
+object setcolor(int r,int g,int b);
+object setpixel(int x,int y [,int r,int g,int b] );
+object line(int x1,int y1,int x2,int y2 [,int r,int g,int b] );
+object box(int x1,int y1,int x2,int y2 [,int r,int g,int b] );
+object circle(int x,int y,int radx,int rady [,int r,int b,int g] );
+object tuned_box(int x1,int y1,int x2,int y2,array(array(int)) corner_rgb);
+
+Information giving methods:
+object xsize();
+object ysize();
+----------------------------------------------------------------------------
+
+METHOD
+     object apply_matrix(array(array(int)) matrix,[int r,int g,int b[,int
+     div]]);
+DESCRIPTION
+     This method applies a matrix on the image. Each surrounding pixel is
+     multiplied with the value of the matrix element in that point, these
+     values are added and divided by the total sum of the matrix values (and
+     the div argument) and stored on the pixel (eventually added to the
+     r,g,b argument given as 'mean' value).
+
+     It is possible to use a matrix of RGB groups (ie an array of three
+     integers) instead of the simple values, this making it possible to
+     apply different matrices on red, green and blue channel.
+RETURN VALUE
+     the new object
+EXAMPLE
+     A 'blur' operation (3x3, gaussian):
+
+     blurred=image->apply_matrix( ({ ({1,2,1}), ({2,3,2}), ({1,2,1}) }) );
+
+     A 'Emboss' operation (3x3):
+
+     emossed=image->apply_matrix(({ ({0,1,8}), ({-1,0,1}), ({-8,-1,0}) }), 128,128,128, 15 );
+
+     Here i'm using 128,128,128 (gray) as a mean, because i get negative
+     values.
+     A division by 15 is good to give 'normal' edges.
+BUGS
+     not known
+
+----------------------------------------------------------------------------
+
+METHOD
+     object autocrop( [int border_width [,int left,int right,int top,int
+     bottom] [,int r,int g,int b] ] );
+DESCRIPTION
+     Crops away unneccesary borders from the image. The border argument is
+     to define the new thickness of the surrounding border and the r,g,b is
+     the newly created border color.
+
+     The left, right, ... arguments is used to tell which edges should be
+     autocropped.
+RETURN VALUE
+     the new object
+EXAMPLE
+
+     cropped=image->autocrop();
+
+BUGS
+     now known
+
+----------------------------------------------------------------------------
+
+METHOD
+     object box(int x1,int y1,int x2,int y2 [,int r,int g,int b] );
+DESCRIPTION
+     Draw a box of the default or specified color.
+RETURN VALUE
+     the image object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object circle(int x,int y,int radx,int rady [,int r,int b,int g] );
+DESCRIPTION
+     Draw a circle. The coordinates given are the center of the image and
+     the radius in x (horisontal) and y (vertical), this making it possible
+     to draw an ellipse too. :-)
+RETURN VALUE
+     the image object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object clone( [int xsize,int ysize [,int r,int g,int b] ] );
+DESCRIPTION
+     make a new object and return it
+        o no arguments -> old image is copied
+        o size is given -> old image is copied cropped
+        o color is given -> new default color
+RETURN VALUE
+     the new object
+SEE ALSO
+     copy, clear
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object color(int r,int g,int b);
+DESCRIPTION
+     Apply a color filter on the image.
+RETURN VALUE
+     the new object
+EXAMPLE
+
+     cyan=image->color(64,255,192);
+
+     This function is most usable on a image that has been grayed first.
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object copy( [int x1,int y1,int x2,int y2 [,int r,int g,int b] ] );
+DESCRIPTION
+     Make a copy, or a copy of a part of the image. It is possible to copy
+     more then the image, to extend the image, this area is filled with the
+     current (or given) color.
+RETURN VALUE
+     the new image object
+EXAMPLE
+
+     copy=image->copy();
+
+     copy=image->copy(-10,-10,image->xsize()+9,image->ysize()+9);
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     string|object fromppm(string s);
+DESCRIPTION
+     Import a ppm image.
+RETURN VALUE
+     0 (object) upon success, else the error message (string).
+EXAMPLE
+
+     image=clone( (program)"precompiled/image" );
+     image->fromppm(read_bytes("my_image.ppm",0,10000000));
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object gray([int r,int g,int b]);
+DESCRIPTION
+     Make this image gray (each r,g,b gets the same value).
+     If a color is given, that specifies the amount of r, g, and b that is
+     used to compute the gray level. Default is 87,127,41.
+RETURN VALUE
+     the new object
+EXAMPLE
+
+     gray=image->gray()
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object invert();
+DESCRIPTION
+     Invert the image.
+RETURN VALUE
+     the new object
+EXAMPLE
+
+     inverted=image->invert()
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object line(int x1,int y1,int x2,int y2 [,int r,int g,int b] );
+DESCRIPTION
+     Draw a line from x1,y1 to x2,y2.
+RETURN VALUE
+     the image object
+EXAMPLE
+
+     image->line(17,100,42,1000);
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object mirrorx(void);
+     object mirrory(void);
+DESCRIPTION
+     Mirrors the image, horisontally or vertically.
+RETURN VALUE
+     the new image object
+EXAMPLE
+
+     mirrored=image->mirrorx();
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object rotate_cw(void);
+     object rotate_ccw(void);
+DESCRIPTION
+     Rotate the image, clockwise or counterclockwise, 90 degrees.
+     This operation is very fast compared to rotating any angle.
+RETURN VALUE
+     the new image object
+EXAMPLE
+
+     snurr=image->rotate_cw();
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object paste(object img [,int x,int y])
+     object paste_alpha(object img, int alpha [,int x, int y]);
+     object paste_mask(object img, object alpha_mask [,int x,int y]);
+DESCRIPTION
+     Paste an image on this image. Use the specified alpha channel value or
+     the second specified image as an alpha channel.
+     The first argument is the image that will be pasted.
+RETURN VALUE
+     the image object this function doesn't return anything
+EXAMPLE
+
+     image->paste(other_smaller_image,17,42);
+
+     image->paste_mask(other_image,alpha_channel_image);
+
+     Paste a dog on a landscape:
+
+     landscape->paste(dog,dog_alpha_channel,xpos,ypos);
+
+     Write some text:
+
+     text=font->write("some text");
+     foreground=text->clear(255,255,255); // white
+     background->paste(foreground,text,xpos,ypos);
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object scale(float factor); (1
+     object scale(float factorx,float factory); (2
+     object scale(int newx|0,int newy|0); (3
+DESCRIPTION
+     Scale this image.
+       1. scale the image with a (line scale) factor
+       2. scale the image with different factors on x and y
+       3. scale the image to a new size
+          with newx or newy set to zero, just scale the image to fit the x
+          or y size and keep proportions.
+RETURN VALUE
+     the new object this function doesn't return anything
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+METHOD
+     object setcolor(int r,int g,int b);
+DESCRIPTION
+     set the default color used for drawing lines, etc
+RETURN VALUE
+     the image object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+METHOD
+     object setpixel(int x,int y [,int r,int g,int b] );
+DESCRIPTION
+     set the color of the specified pixel
+RETURN VALUE
+     the image object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object threshold([int r,int g,int b]);
+DESCRIPTION
+     make image black-and-white using the given value as the threshold
+RETURN VALUE
+     the new object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     string togif( [int r,inr g,int b] );
+DESCRIPTION
+     export gif
+     if the color are given, this is the transparent color
+RETURN VALUE
+     the gifimage as a string
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     string toppm(object);
+DESCRIPTION
+     export ppm
+RETURN VALUE
+     the ppm image as a string
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object tuned_box(int x1,int y1,int x2,int y2,array(array(int))
+     corner_rgb);
+DESCRIPTION
+     draw a box with the specified corner colours, and shade the colors
+     between
+RETURN VALUE
+     the image object
+EXAMPLE
+
+     image->tuned_box(0,0,img->xsize()-1,img->ysize()-1,
+                     ({({0,0,64}),({16,16,128}),
+                       ({16,16,128}),({192,160,128})}));
+
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object xsize();
+     object ysize();
+DESCRIPTION
+RETURN VALUE
+     Gives the x- or the y-size (horisontal or vertical size) of the image.
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+methods in precompiled/font:
+
+int load(string file_name);
+object write(string line, ...);
+----------------------------------------------------------------------------
+
+METHOD
+     int load(string file_name);
+DESCRIPTION
+     load this font object with the font from the specified file
+RETURN VALUE
+     true on success
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+METHOD
+     object write(string line, ...);
+DESCRIPTION
+     make a new image object from the specified text, each argument
+     representing a line
+RETURN VALUE
+     the new image object
+EXAMPLE
+BUGS
+
+----------------------------------------------------------------------------
+
+Example program:
+
+(pike)
+
+int main()
+{
+   object txt,o,shad,font;
+   int i;
+
+   txt =
+      (font=clone((program)"/precompiled/font"))
+      ->load("/usr/local/lib/pike/fonts/64/helvetica_bold_r")
+      ->write("The Image Module")
+      ->autocrop(20,0,0,0);
+
+   shad=txt->mirrory()->scale(1.0,0.3)->color(64,64,64);
+
+   o=clone((program)"/precompiled/image",
+           txt->xsize(),txt->ysize(), 0,0,100)
+        ->tuned_box(0,0,txt->xsize(),txt->ysize(),
+                    ({({0,0,0}),({0,0,0}),
+                      ({0,0,255}),({128,128,0})}));
+
+   o->setcolor(255,255,255,200);
+   for (i=0; i<30; i++)
+      if (random(2))
+         o->line(random(o->xsize()),o->ysize()-10-random(20+i*3),
+                 o->xsize()-1-random(30),o->ysize()-1);
+      else
+         o->line(random(o->xsize()),o->ysize()-10-random(20+i),
+                 random(30),o->ysize()-1);
+
+   for (i=0; i<10; i++)
+      o->box(random(o->xsize()),random(o->ysize()),
+             random(o->xsize()),random(o->ysize()),
+             random(256),random(256),random(256),220);
+
+   o -> paste_mask(txt->clear(0,255,0),
+                   shad,0,(int)(font->baseline()*0.7)+shad->ysize()-10)
+     -> paste_mask(txt->clear(255,255,0),
+                 txt->apply_matrix(({({1,2,1}),({2,4,2}),({1,2,1})}))
+                 ->apply_matrix(({({1,2,1}),({2,4,2}),({1,2,1})}))
+                 ->modify_by_intensity(1,0,0, 0,255,255,255,255,255))
+     -> paste_mask(txt->clone()
+                   ->tuned_box(0,0,txt->xsize()-1,txt->ysize()-1,
+                               ({({128,128,128}),({64,128,0}),
+                                 ({64,128,0}),({255,255,0})})),
+                   txt);
+   write(o->togif_fs());
+   return 0;
+}
diff --git a/src/modules/Image/doc/image.html b/src/modules/Image/doc/image.html
new file mode 100644
index 0000000000000000000000000000000000000000..10dca57176c0858f7754b0d1aa597b9fac189827
--- /dev/null
+++ b/src/modules/Image/doc/image.html
@@ -0,0 +1,568 @@
+<!-- $id$ -->
+
+<center>
+<i>Pike module: </i>
+<h1>image</h1>
+Pontus Hagland <a href=law@infovav.se><i>law@infovav.se</i></a>
+<br>Per Hedbor <a href=per@infovav.se><i>per@infovav.se</i></a>
+<br>David K�gedal <a href=kg@infovav.se><i>kg@infovav.se</i></a>
+</center>
+
+<hr>
+
+This package adds two Pike progams:
+
+<ul>
+<li><tt><a href=#image>"precompiled/image"</a></tt> and
+<li><tt><a href=#font>"precompiled/font"</a></tt>.
+</ul>
+
+<hr>
+
+<a name=image><h2>methods in precompiled/image:</h2></a>
+
+Methods resulting in a new object:
+
+<br><a name=cloner><tt>object <a href=#clone><b>clone</b></a>( </tt>[<tt>int <b>xsize</b>,int <b>ysize</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<p><a name=copyr><tt>object <a href=#copy><b>copy</b></a>( </tt>[<tt>int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<br><a name=autocropr><tt>object <a href=#autocrop><b>autocrop</b></a>( </tt>[<tt>int <b>border_width</b> </tt>[<tt>,int <b>left</b>,int <b>right</b>,int <b>top</b>,int <b>bottom</b></tt>]<tt></b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<p><a name=grayr><tt>object <a href=#gray><b>gray</b></a>();</tt></a>
+<br><a name=colorr><tt>object <a href=#color><b>color</b></a>(int <b>r</b>,int <b>g</b>,int <b>b</b>);</tt></a>
+<br><a name=invertr><tt>object <a href=#invert><b>invert</b></a>();</tt></a>
+
+<p><a name=mirrorxr><tt>object <a href=#mirrorx><b>mirrorx</b></a>(void);</tt></a>
+<br><a name=mirroryr><tt>object <a href=#mirrory><b>mirrory</b></a>(void);</tt></a>
+<br><a name=rotate_cwr><tt>object <a href=#rotate_cw><b>rotate_cw</b></a>(void);</tt></a>
+<br><a name=rotate_ccwr><tt>object <a href=#rotate_ccw><b>rotate_ccw</b></a>(void);</tt></a>
+<br><a name=thresholdr><tt>object <a href=#threshold><b>threshold</b></a>(</tt>[<tt>int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt></b>); </tt></a>
+<br><a name=apply_matrixr><tt>object <a href=#apply_matrix><b>apply_matrix</b></a>(array(array(int)) <b>matrix</b>,</tt>[<tt>int <b>r</b>,int <b>g</b>,int <b>b</b></tt>[<tt>,int <b>div</b></tt>]]<tt>);</tt></a>
+<p><a name=scaler><tt>object <a href=#scale><b>scale</b></a>(float <b>factor</b>);</tt></a>
+<br><a name=scaler><tt>object <a href=#scale><b>scale</b></a>(float <b>factorx</b>,float <b>factory</b>);</tt></a>
+<br><a name=scaler><tt>object <a href=#scale><b>scale</b></a>(int <b>newx</b></tt>|<tt><b>0</b>,int <b>newy</b></tt>|<tt><b>0</b>);</tt></a>
+
+<p>Methods operating on current object:
+
+<br><a name=toppmr><tt>string <a href=#toppm><b>toppm</b></a>(void</b>);</tt></a>
+<br><a name=fromppmr><tt>string|object <a href=#fromppm><b>fromppm</b></a>(string <b>s</b>);</tt></a>
+<br><a name=togifr><tt>string <a href=#togif><b>togif</b></a>( </tt>[<tt>int <b>r</b>,inr g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<p><a name=paster><tt>object <a href=#paste><b>paste</b></a>(object <b>img</b> </tt>[<tt>,int <b>x</b>,int <b>y</b></tt>]<tt></b>)</tt></a>
+<br><a name=paste_alphar><tt>object <a href=#paste_alpha><b>paste_alpha</b></a>(object <b>img</b>, int <b>alpha</b> </tt>[<tt>,int <b>x</b>, int <b>y</b></tt>]<tt></b>);</tt></a>
+<br><a name=paste_maskr><tt>object <a href=#paste_mask><b>paste_mask</b></a>(object <b>img</b>, object <b>alpha_mask</b> </tt>[<tt>,int <b>x</b>,int <b>y</b></tt>]<tt></b>);</tt></a>
+<p><a name=setcolorr><tt>object <a href=#setcolor><b>setcolor</b></a>(int <b>r</b>,int <b>g</b>,int <b>b</b>);</tt></a>
+<br><a name=setpixelr><tt>object <a href=#setpixel><b>setpixel</b></a>(int <b>x</b>,int <b>y</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<br><a name=liner><tt>object <a href=#line><b>line</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<br><a name=boxr><tt>object <a href=#box><b>box</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<br><a name=circler><tt>object <a href=#circle><b>circle</b></a>(int <b>x</b>,int <b>y</b>,int <b>radx</b>,int <b>rady</b> </tt>[<tt>,int <b>r</b>,int <b>b</b>,int <b>g</b></tt>]<tt> </b>);</tt></a>
+<br><a name=tuned_boxr><tt>object <a href=#tuned_box><b>tuned_box</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b>,array(array(int)) corner_rgb</b>);</tt></a>
+
+<p>Information giving methods:
+<br><a name=xsizer><tt>object <a href=#xsize><b>xsize</b></a>();</tt></a>
+<br><a name=ysizer><tt>object <a href=#ysize><b>ysize</b></a>();</tt></a>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=apply_matrix><tt>object <a href=#apply_matrixr><b>apply_matrix</b></a>(array(array(int)) <b>matrix</b>,</tt>[<tt>int <b>r</b>,int <b>g</b>,int <b>b</b></tt>[<tt>,int <b>div</b></tt>]]<tt>);</tt></a>
+<dt>DESCRIPTION
+
+<dd>This method applies a matrix on the image. Each surrounding pixel
+is multiplied with the value of the matrix element in that point,
+these values are added and divided by the total sum of the matrix
+values (and the <b>div</b> argument) and stored on the pixel
+(eventually added to the <b>r</b>,<b>g</b>,<b>b</b> argument given as
+'mean' value).
+
+<p>It is possible to use a matrix of RGB groups (ie an array of three
+integers) instead of the simple values, this making it possible to
+apply different matrices on red, green and blue channel.
+
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd>
+A 'blur' operation (3x3, gaussian):
+<pre>blurred=image->apply_matrix( ({ ({1,2,1}), ({2,3,2}), ({1,2,1}) }) );</pre>
+<p>A 'Emboss' operation (3x3):
+<pre>emossed=image->apply_matrix(({ ({0,1,8}), ({-1,0,1}), ({-8,-1,0}) }), 128,128,128, 15 );</pre>
+Here i'm using 128,128,128 (gray) as a mean, because i get negative values.
+<br>A division by 15 is good to give 'normal' edges.
+
+<dt>BUGS
+<dd>not known
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=autocrop><tt>object <a href=#autocropr><b>autocrop</b></a>( </tt>[<tt>int <b>border_width</b> </tt>[<tt>,int <b>left</b>,int <b>right</b>,int <b>top</b>,int <b>bottom</b></tt>]<tt></b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+
+<dd>Crops away unneccesary borders from the image. The <b>border</b>
+argument is to define the new thickness of the surrounding border and
+the <b>r</b>,<b>g</b>,<b>b</b> is the newly created border color.
+
+<p>The <b>left</b>, <b>right</b>, ... arguments is used to tell which
+edges should be autocropped.
+
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd><pre>cropped=image->autocrop();</pre>
+<dt>BUGS
+<dd>now known
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=box><tt>object <a href=#boxr><b>box</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+
+<dd>Draw a box of the default or specified color.
+
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=circle><tt>object <a href=#circler><b>circle</b></a>(int <b>x</b>,int <b>y</b>,int <b>radx</b>,int <b>rady</b> </tt>[<tt>,int <b>r</b>,int <b>b</b>,int <b>g</b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Draw a circle. The coordinates given are the center of the image and the radius in x (horisontal) and y (vertical), this making it possible to draw an ellipse too. <tt>:-)</tt>
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=clone><tt>object <a href=#cloner><b>clone</b></a>( </tt>[<tt>int <b>xsize</b>,int <b>ysize</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>make a new object and return it
+<ul>
+<li>no arguments -> old image is copied
+<li>size is given -> old image is copied cropped
+<li>color is given -> new default color
+</ul>
+<dt>RETURN VALUE
+<dd>the new object
+<dt>SEE ALSO
+<dd><a href=#copy>copy</a>, <a href=#clear>clear</a>
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=color><tt>object <a href=#colorr><b>color</b></a>(int <b>r</b>,int <b>g</b>,int <b>b</b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Apply a color filter on the image. 
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd><pre>cyan=image->color(64,255,192);</pre>
+This function is most usable on a image that has been <a href=#gray>gray</a>ed first.
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=copy><tt>object <a href=#copyr><b>copy</b></a>( </tt>[<tt>int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Make a copy, or a copy of a part of the image. 
+    It is possible to copy more then the image, to extend the image,
+    this area is filled with the current (or given) color.
+<dt>RETURN VALUE
+<dd>the new image object
+<dt>EXAMPLE
+<dd><pre>copy=image->copy();
+
+copy=image->copy(-10,-10,image->xsize()+9,image->ysize()+9);</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=fromppm><tt>string|object <a href=#fromppmr><b>fromppm</b></a>(string <b>s</b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Import a ppm image.
+<dt>RETURN VALUE
+<dd>0 (object) upon success, else the error message (string).
+<dt>EXAMPLE
+<dd><pre>image=clone( (program)"precompiled/image" );
+image->fromppm(read_bytes("my_image.ppm",0,10000000));</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=gray><tt>object <a href=#grayr><b>gray</b></a>(</tt>[<tt>int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt>);</tt></a>
+<dt>DESCRIPTION
+
+<dd>Make this image gray (each r,g,b gets the same value).<br> If a
+color is given, that specifies the amount of r, g, and b that is used
+to compute the gray level. Default is 87,127,41.
+
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd><pre>gray=image->gray()</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=invert><tt>object <a href=#invertr><b>invert</b></a>();</tt></a>
+<dt>DESCRIPTION
+<dd>Invert the image.
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd><pre>inverted=image->invert()</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=line><tt>object <a href=#liner><b>line</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Draw a line from <b>x1</b>,<b>y1</b> to <b>x2</b>,<b>y2</b>.
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd><pre>image->line(17,100,42,1000);</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=mirrorx><tt>object <a href=#mirrorxr><b>mirrorx</b></a>(void);</tt></a>
+<dd><a name=mirrory><tt>object <a href=#mirroryr><b>mirrory</b></a>(void);</tt></a>
+<dt>DESCRIPTION
+<dd>Mirrors the image, horisontally or vertically.
+<dt>RETURN VALUE
+<dd>the new image object
+<dt>EXAMPLE
+<dd><pre>mirrored=image->mirrorx();</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+<dl>
+<dt>METHOD
+<dd><a name=rotate_cw><tt>object <a href=#rotate_cwr><b>rotate_cw</b></a>(void);</tt></a>
+<dd><a name=rotate_ccw><tt>object <a href=#rotate_ccwr><b>rotate_ccw</b></a>(void);</tt></a>
+<dt>DESCRIPTION
+<dd>Rotate the image, clockwise or counterclockwise, 90 degrees.
+<br>This operation is very fast compared to rotating any angle.
+<dt>RETURN VALUE
+<dd>the new image object
+<dt>EXAMPLE
+<dd><pre>snurr=image->rotate_cw();</pre>
+<dt>BUGS
+<dd>
+</dl>
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=paste><tt>object <a href=#paster><b>paste</b></a>(object <b>img</b> </tt>[<tt>,int <b>x</b>,int <b>y</b></tt>]<tt></b>)</tt></a>
+<dd><a name=paste_alpha><tt>object <a href=#paste_alphar><b>paste_alpha</b></a>(object <b>img</b>, int <b>alpha</b> </tt>[<tt>,int <b>x</b>, int <b>y</b></tt>]<tt></b>);</tt></a>
+<dd><a name=paste_mask><tt>object <a href=#paste_maskr><b>paste_mask</b></a>(object <b>img</b>, object <b>alpha_mask</b> </tt>[<tt>,int <b>x</b>,int <b>y</b></tt>]<tt></b>);</tt></a>
+<dt>DESCRIPTION
+<dd>Paste an image on this image. Use the specified alpha channel
+value or the second specified image as an alpha channel.<br>
+The first argument is the image that will be pasted.
+<dt>RETURN VALUE
+<dd>the image object
+this function doesn't return anything
+<dt>EXAMPLE
+<dd><pre>image->paste(other_smaller_image,17,42);
+
+image->paste_mask(other_image,alpha_channel_image);</pre>
+Paste a dog on a landscape:<pre>landscape->paste(dog,dog_alpha_channel,xpos,ypos);</pre>
+Write some text:<pre>text=font->write("some text");
+foreground=text->clear(255,255,255); // white
+background->paste(foreground,text,xpos,ypos);</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=scale><tt>object <a href=#scaler><b>scale</b></a>(float <b>factor</b>);</tt> (1</a>
+<dd><a name=scale><tt>object <a href=#scaler><b>scale</b></a>(float <b>factorx</b>,float <b>factory</b>);</tt> (2</a>
+<dd><a name=scale><tt>object <a href=#scaler><b>scale</b></a>(int <b>newx</b></tt>|<tt><b>0</b>,int <b>newy</b></tt>|<tt><b>0</b>);</tt> (3</a>
+<dt>DESCRIPTION
+<dd>Scale this image.
+<ol>
+<li>scale the image with a (line scale) factor
+<li>scale the image with different factors on x and y
+<li>scale the image to a new size
+<br>with newx or newy set to zero, just scale the image to fit the x
+or y size and keep proportions.
+</ol>
+<dt>RETURN VALUE
+<dd>the new object
+this function doesn't return anything
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dt>METHOD
+<dd><a name=setcolor><tt>object <a href=#setcolorr><b>setcolor</b></a>(int <b>r</b>,int <b>g</b>,int <b>b</b>);</tt></a>
+<dt>DESCRIPTION
+<dd>set the default color used for drawing lines, etc
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dt>METHOD
+<dd><a name=setpixel><tt>object <a href=#setpixelr><b>setpixel</b></a>(int <b>x</b>,int <b>y</b> </tt>[<tt>,int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>set the color of the specified pixel
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=threshold><tt>object <a href=#thresholdr><b>threshold</b></a>(</tt>[<tt>int <b>r</b>,int <b>g</b>,int <b>b</b></tt>]<tt></b>); </tt></a>
+<dt>DESCRIPTION
+<dd>make image black-and-white using the given value as the threshold
+<dt>RETURN VALUE
+<dd>the new object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=togif><tt>string <a href=#togifr><b>togif</b></a>( </tt>[<tt>int <b>r</b>,inr g</b>,int <b>b</b></tt>]<tt> </b>);</tt></a>
+<dt>DESCRIPTION
+<dd>export gif
+<br>if the color are given, this is the transparent color
+<dt>RETURN VALUE
+<dd>the gifimage as a string
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=toppm><tt>string <a href=#toppmr><b>toppm</b></a>(object</b>);</tt></a>
+<dt>DESCRIPTION
+<dd>export ppm
+<dt>RETURN VALUE
+<dd>the ppm image as a string
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=tuned_box><tt>object <a href=#tuned_boxr><b>tuned_box</b></a>(int <b>x1</b>,int <b>y1</b>,int <b>x2</b>,int <b>y2</b>,array(array(int)) corner_rgb</b>);</tt></a>
+<dt>DESCRIPTION
+<dd>draw a box with the specified corner colours, and shade the colors between
+<dt>RETURN VALUE
+<dd>the image object
+<dt>EXAMPLE
+<dd><pre>image->tuned_box(0,0,img->xsize()-1,img->ysize()-1,
+	        ({({0,0,64}),({16,16,128}),
+                  ({16,16,128}),({192,160,128})}));</pre>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+<dl>
+<dt>METHOD
+<dd><a name=xsize><tt>object <a href=#xsizer><b>xsize</b></a>();</tt></a>
+<dd><a name=ysize><tt>object <a href=#ysizer><b>ysize</b></a>();</tt></a>
+<dt>DESCRIPTION
+<dd>
+<dt>RETURN VALUE
+<dd>Gives the x- or the y-size (horisontal or vertical size) of the image.
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+
+<hr>
+
+<a name=font><h2>methods in precompiled/font:</h2></a>
+
+<a name=loadr><tt>int <a href=#load><b>load</b></a>(string <b>file_name</b>);</tt>
+<br><a name=writer><tt>object <a href=#write><b>write</b></a>(string <b>line</b>, </tt>...<tt>);</tt>
+
+<hr>
+<dl>
+<dt>METHOD
+<dd><a name=load><tt>int <a href=#loadr><b>load</b></a>(string <b>file_name</b>);</tt>
+<dt>DESCRIPTION
+<dd>load this font object with the font from the specified file
+<dt>RETURN VALUE
+<dd>true on success
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<dl>
+<dt>METHOD
+<dd><a name=write><tt>object <a href=#writer><b>write</b></a>(string <b>line</b>, </tt>...<tt>);</tt>
+<dt>DESCRIPTION
+<dd>make a new image object from the specified text, each argument representing a line
+<dt>RETURN VALUE
+<dd>the new image object
+<dt>EXAMPLE
+<dd>
+<dt>BUGS
+<dd>
+</dl>
+
+<hr>
+
+<h2>Example program:</h2>
+(pike)
+<pre>
+
+int main()
+{
+   object txt,o,shad,font;
+   int i;
+
+   txt = 
+      (font=clone((program)"/precompiled/font"))
+      ->load("/usr/local/lib/pike/fonts/64/helvetica_bold_r")
+      ->write("The Image Module")
+      ->autocrop(20,0,0,0);
+
+   shad=txt->mirrory()->scale(1.0,0.3)->color(64,64,64);
+
+   o=clone((program)"/precompiled/image",
+	   txt->xsize(),txt->ysize(), 0,0,100)
+        ->tuned_box(0,0,txt->xsize(),txt->ysize(),
+		    ({({0,0,0}),({0,0,0}),
+		      ({0,0,255}),({128,128,0})}));
+   
+   o->setcolor(255,255,255,200);
+   for (i=0; i<30; i++)
+      if (random(2))
+	 o->line(random(o->xsize()),o->ysize()-10-random(20+i*3),
+		 o->xsize()-1-random(30),o->ysize()-1);
+      else
+	 o->line(random(o->xsize()),o->ysize()-10-random(20+i),
+		 random(30),o->ysize()-1);
+
+   for (i=0; i<10; i++)
+      o->box(random(o->xsize()),random(o->ysize()),
+	     random(o->xsize()),random(o->ysize()),
+	     random(256),random(256),random(256),220);
+
+   o -> paste_mask(txt->clear(0,255,0),
+		   shad,0,(int)(font->baseline()*0.7)+shad->ysize()-10)
+     -> paste_mask(txt->clear(255,255,0),
+		 txt->apply_matrix(({({1,2,1}),({2,4,2}),({1,2,1})}))
+		 ->apply_matrix(({({1,2,1}),({2,4,2}),({1,2,1})}))
+		 ->modify_by_intensity(1,0,0, 0,255,255,255,255,255))
+     -> paste_mask(txt->clone()
+		   ->tuned_box(0,0,txt->xsize()-1,txt->ysize()-1,
+			       ({({128,128,128}),({64,128,0}),
+                                 ({64,128,0}),({255,255,0})})),
+		   txt);
+   write(o->togif_fs());
+   return 0;
+}
+</pre>
+
+<h2>Undocumented, yet:</h2>
+<pre>
+object image->select_from(int x,int y);
+object image->distancesq(int r,int g,int b);
+array(int) image->getpixel(int x,int y);
+object image->skewx(int diff,rgb);
+object image->skewy(int diff,rgb);
+object image->skewx_expand(int diff,rgb);
+object image->skewy_expand(int diff,rgb);
+object image->rotate(int|float angle,rgb);
+object image->rotate_expand(int|float angle,rgb);
+object image->turbulence(colorrange,int octaves=3,float scale=1,
+			 float xdiff=0,float ydiff=0,float cscale=1);
+object image->noise(colorrange,float scale=0.1,
+	 	    float xdiff=0,float ydiff=0,float cscale=1);
+    where colorrange is ({ float position=0..1, ({r,g,b}),
+       			   float position=0..1, ({r,g,b}), ... })
+</pre>
\ No newline at end of file
diff --git a/src/modules/Image/font.c b/src/modules/Image/font.c
new file mode 100644
index 0000000000000000000000000000000000000000..8e3aede357ce80881dcb97bac9f16340232db885
--- /dev/null
+++ b/src/modules/Image/font.c
@@ -0,0 +1,572 @@
+#include <config.h>
+
+/* $Id: font.c,v 1.1 1997/02/11 08:35:43 hubbe Exp $ */
+
+#include "global.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef HAVE_SYS_FCNTL_H
+#include <sys/fcntl.h>
+#endif
+
+#include <netinet/in.h>
+#include <errno.h>
+
+#include "config.h"
+
+#include "stralloc.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "array.h"
+#include "threads.h"
+
+#include "image.h"
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+
+static struct program *font_program;
+extern struct program *image_program;
+
+#define THIS (*(struct font **)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+struct font 
+{
+   unsigned long height;      /* height of character rectangles */
+   unsigned long baseline;    /* baseline of characters */
+#ifdef HAVE_MMAP
+   unsigned long mmaped_size; /* if 0 - not mmaped: just free() mem */
+#endif
+   void *mem;         /* pointer to mmaped/malloced memory */
+   unsigned long chars;       /* number of characters */
+  float xspacing_scale; /* Fraction of spacing to use */
+  float yspacing_scale; /* Fraction of spacing to use */
+  enum {
+    J_LEFT,
+    J_RIGHT,
+    J_CENTER
+  } justification;
+   struct _char      
+   {
+      unsigned long width;   /* character rectangle has this width in pixels */
+      unsigned long spacing; /* pixels to next character */
+      unsigned char *pixels; /* character rectangle */
+   } charinfo [1]; /* many!! */
+};
+
+/***************** init & exit *********************************/
+
+static inline void free_font_struct(struct font *font)
+{
+   if (font)
+   {
+      if (font->mem)
+      {
+#ifdef HAVE_MMAP
+	 munmap(font->mem,font->mmaped_size);
+#else
+	 free(font->mem);
+#endif
+      }
+      free(font);
+   }
+}
+
+static void init_font_struct(struct object *o)
+{
+  THIS=NULL;
+}
+
+static void exit_font_struct(struct object *obj)
+{
+   free_font_struct(THIS);
+   THIS=NULL;
+}
+
+/***************** internals ***********************************/
+
+
+static inline int my_read(int from, char *buf, int towrite)
+{
+  int res;
+  while((res = read(from, buf, towrite)) < 0)
+  {
+    switch(errno)
+    {
+     case EAGAIN: case EINTR:
+      continue;
+
+     default:
+      res = 0;
+      return 0;
+    }
+  }
+  return res;
+}
+
+static inline long file_size(int fd)
+{
+  struct stat tmp;
+  int res;
+  if(!fstat(fd, &tmp)) return res = tmp.st_size;
+  return -1;
+}
+
+static inline write_char(struct _char *ci,
+			 rgb_group *pos,
+			 INT32 xsize,
+			 INT32 height)
+{
+   rgb_group *nl;
+   INT32 x,y;
+   unsigned char *p;
+
+   p=ci->pixels;
+
+   for (y=height; y>0; y--)
+   {
+      nl=pos+xsize;
+      for (x=(INT32)ci->width; x>0; x--)
+      {
+	 register char c;
+	 c=255-*p;
+	 if (pos->r==0)
+	    pos->r=pos->g=pos->b=c;
+	 else if (pos->r+c>255)
+	    pos->r=pos->g=pos->b=255;
+	 else
+	 {
+	    pos->r+=c;
+	    pos->g+=c;
+	    pos->b+=c;
+	 }
+	 pos++;
+	 p++;
+      }
+      pos=nl;
+   }
+}
+
+/***************** methods *************************************/
+
+
+void font_load(INT32 args)
+{
+   int fd;
+
+   if (args<1 
+       || sp[-args].type!=T_STRING)
+      error("font->read: illegal or wrong number of arguments\n");
+   
+   if (THIS)
+   {
+      free_font_struct(THIS);
+      THIS=NULL;
+   }
+   do 
+   {
+#ifdef FONT_DEBUG
+     fprintf(stderr,"FONT open '%s'\n",sp[-args].u.string->str);
+#endif
+     fd = open(sp[-args].u.string->str,O_RDONLY);
+   } while(fd < 0 && errno == EINTR);
+
+   if (fd >= 0)
+   {
+      long size;
+      struct font *new;
+
+      size = file_size(fd);
+      if (size > 0)
+      {
+	 new=THIS=(struct font *)xalloc(sizeof(struct font));
+
+	 THREADS_ALLOW();
+#ifdef HAVE_MMAP
+	 new->mem = 
+	    mmap(0,size,PROT_READ,MAP_SHARED,fd,0);
+	 new->mmaped_size=size;
+#else
+	 new->mem = malloc(size);
+	 if ((new->mem) && (!my_read(fd,new->mem,size))) {
+	   free(new->mem);
+	   new->mem = NULL;
+	 }
+#endif
+	 THREADS_DISALLOW();
+
+	 if (THIS->mem)
+	 {
+	    int i;
+
+	    struct file_head 
+	    {
+	       unsigned INT32 cookie;
+	       unsigned INT32 version;
+	       unsigned INT32 chars;
+	       unsigned INT32 height;
+	       unsigned INT32 baseline;
+	       unsigned INT32 o[1];
+	    } *fh;
+	    struct char_head
+	    {
+	       unsigned INT32 width;
+	       unsigned INT32 spacing;
+	       unsigned char data[1];
+	    } *ch;
+
+#ifdef FONT_DEBUG
+	    fprintf(stderr,"FONT mapped ok\n");
+#endif
+
+	    fh=(struct file_head*)THIS->mem;
+
+	    if (ntohl(fh->cookie)==0x464f4e54) /* "FONT" */
+	    {
+#ifdef FONT_DEBUG
+	       fprintf(stderr,"FONT cookie ok\n");
+#endif
+	       if (ntohl(fh->version)==1)
+	       {
+		  unsigned long i;
+
+#ifdef FONT_DEBUG
+		  fprintf(stderr,"FONT version 1\n");
+#endif
+
+		  THIS->chars=ntohl(fh->chars);
+
+		  new=malloc(sizeof(struct font)+
+			     sizeof(struct _char)*(THIS->chars-1));
+		  new->mem=THIS->mem;
+#ifdef HAVE_MMAP
+		  new->mmaped_size=THIS->mmaped_size;
+#endif
+		  new->chars=THIS->chars;
+		  new->xspacing_scale = 1.0;
+		  new->yspacing_scale = 1.0;
+		  new->justification = J_LEFT;
+		  free(THIS);
+		  THIS=new;
+
+		  THIS->height=ntohl(fh->height);
+		  THIS->baseline=ntohl(fh->baseline);
+
+		  for (i=0; i<THIS->chars; i++)
+		  {
+		     if (i*sizeof(INT32)<(unsigned long)size
+			 && ntohl(fh->o[i])<(unsigned long)size
+			 && ! ( ntohl(fh->o[i]) % 4) ) /* must be aligned */
+		     {
+			ch=(struct char_head*)
+			   ((char *)(THIS->mem)+ntohl(fh->o[i]));
+			THIS->charinfo[i].width = ntohl(ch->width);
+			THIS->charinfo[i].spacing = ntohl(ch->spacing);
+			THIS->charinfo[i].pixels = ch->data;
+		     }
+		     else /* illegal <tm> offset or illegal align */
+		     {
+#ifdef FONT_DEBUG
+			fprintf(stderr,"FONT failed on char %02xh %d '%c'\n",
+				i,i,i);
+#endif
+			free_font_struct(new);
+			THIS=NULL;
+			pop_n_elems(args);
+			push_int(0);
+			return;
+		     }
+
+		  }
+
+		  close(fd);
+		  pop_n_elems(args);
+		  THISOBJ->refs++;
+		  push_object(THISOBJ);   /* success */
+#ifdef FONT_DEBUG
+		  fprintf(stderr,"FONT successfully loaded\n");
+#endif
+		  return;
+	       } /* wrong version */
+#ifdef FONT_DEBUG
+	       else fprintf(stderr,"FONT unknown version\n");
+#endif
+	    } /* wrong cookie */
+#ifdef FONT_DEBUG
+	    else fprintf(stderr,"FONT wrong cookie\n");
+#endif
+	 } /* mem failure */
+#ifdef FONT_DEBUG
+	 else fprintf(stderr,"FONT mem failure\n");
+#endif
+	 free_font_struct(THIS);
+	 THIS=NULL;
+      } /* size failure */
+#ifdef FONT_DEBUG
+      else fprintf(stderr,"FONT size failure\n");
+#endif
+      close(fd);
+   } /* fd failure */
+#ifdef FONT_DEBUG
+   else fprintf(stderr,"FONT fd failure\n");
+#endif
+
+   pop_n_elems(args);
+   push_int(0);
+   return;
+}
+
+void font_write(INT32 args)
+{
+   struct object *o;
+   struct image *img;
+   INT32 xsize=0,i,maxwidth,c,maxwidth2,j;
+   int *width_of;
+
+   if (!THIS)
+      error("font->write: no font loaded\n");
+
+   maxwidth2=0;
+
+   width_of=(int *)malloc((args+1)*sizeof(int));
+   if(!width_of)
+     error("Out of memory\n");
+   for (j=0; j<args; j++)
+   {
+     if (sp[j-args].type!=T_STRING)
+       error("font->write: illegal argument(s)\n");
+     
+     xsize = 0;
+     maxwidth = 0;
+     
+     for (i = 0; i < sp[j-args].u.string->len; i++)
+     {
+       c=EXTRACT_UCHAR(sp[j-args].u.string->str+i);
+       if (c < (INT32)THIS->chars)
+       {
+	 if (xsize + (signed long)THIS->charinfo[c].width > maxwidth)
+	   maxwidth = xsize + THIS->charinfo[c].width;
+	 xsize+=(signed long)((float)THIS->charinfo[c].spacing
+			      *(float)THIS->xspacing_scale);
+       }
+     }
+     if (xsize>maxwidth) maxwidth=xsize;
+     width_of[j]=maxwidth;
+     if (maxwidth>maxwidth2) maxwidth2=maxwidth;
+   }
+   
+   o = clone(image_program,0);
+   img = ((struct image*)o->storage);
+   img->xsize = maxwidth2;
+   if(args>1)
+     img->ysize = THIS->height+((double)THIS->height*(double)(args-1)*(double)THIS->yspacing_scale)+1;
+   else
+     img->ysize = THIS->height+1;
+   img->rgb.r=img->rgb.g=img->rgb.b=255;
+   img->img=malloc(img->xsize*img->ysize*sizeof(rgb_group));
+
+   if (!img) { free_object(o); free(width_of); error("Out of memory\n"); }
+
+   MEMSET(img->img,0,img->xsize*img->ysize*sizeof(rgb_group));
+
+   for (j=0; j<args; j++)
+   {
+     switch(THIS->justification)
+     {
+      case J_LEFT: xsize = 0; break;
+      case J_RIGHT: xsize = img->xsize-width_of[j]-1; break;
+      case J_CENTER: xsize = img->xsize/2-width_of[j]/2-1; break;
+     }
+     if(xsize<0) xsize=0;
+     for (i = 0; i < (int)sp[j-args].u.string->len; i++)
+     {
+       c=EXTRACT_UCHAR(sp[j-args].u.string->str+i);
+       if ( c < (INT32)THIS->chars)
+       {
+	 write_char(THIS->charinfo+c,
+		    (img->img+xsize)+(img->xsize*(int)(j*THIS->height
+						 *THIS->yspacing_scale)),
+		    img->xsize,
+		    THIS->height);
+	 xsize += THIS->charinfo[c].spacing*THIS->xspacing_scale;
+       }
+     }
+   }
+   free(width_of);
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void font_height(INT32 args)
+{
+   pop_n_elems(args);
+   if (THIS)
+      push_int(THIS->height);
+   else
+      push_int(0);
+}
+
+void font_text_extents(INT32 args)
+{
+  INT32 xsize,i,maxwidth,c,maxwidth2,j;
+
+  if (!THIS)
+    error("font->text_extents: no font loaded\n");
+
+  maxwidth2=0;
+
+  for (j=0; j<args; j++)
+  {
+    if (sp[j-args].type!=T_STRING)
+      error("font->text_extents: illegal argument(s)\n");
+    
+    xsize = 0;
+    maxwidth = 0;
+     
+    for (i = 0; i < sp[j-args].u.string->len; i++)
+    {
+      c=EXTRACT_UCHAR(sp[j-args].u.string->str+i);
+      if (c < (INT32)THIS->chars)
+      {
+	if (xsize + (signed long)THIS->charinfo[c].width > maxwidth)
+	  maxwidth = xsize + THIS->charinfo[c].width;
+	xsize += THIS->charinfo[c].spacing*THIS->xspacing_scale;
+      }
+    }
+    
+    if (xsize>maxwidth) maxwidth=xsize;
+    if (maxwidth>maxwidth2) maxwidth2=maxwidth;
+  }
+  push_int(maxwidth2);
+  push_int(args * THIS->height * THIS->yspacing_scale);
+}
+
+
+void font_set_xspacing_scale(INT32 args)
+{
+  if(!THIS) error("font->set_xspacing_scale(FLOAT): No font loaded.\n");
+  if(!args) error("font->set_xspacing_scale(FLOAT): No argument!\n");
+  if(sp[-args].type!=T_FLOAT)
+    error("font->set_xspacing_scale(FLOAT): Wrong type of argument!\n");
+
+  THIS->xspacing_scale = (double)sp[-args].u.float_number;
+/*fprintf(stderr, "Setting xspacing to %f\n", THIS->xspacing_scale);*/
+  if(THIS->xspacing_scale < 0.0)
+    THIS->xspacing_scale=0.1;
+  pop_stack();
+}
+
+
+void font_set_yspacing_scale(INT32 args)
+{
+  if(!THIS) error("font->set_yspacing_scale(FLOAT): No font loaded.\n");
+  if(!args) error("font->set_yspacing_scale(FLOAT): No argument!\n");
+  if(sp[-args].type!=T_FLOAT)
+    error("font->set_yspacing_scale(FLOAT): Wrong type of argument!\n");
+
+  THIS->yspacing_scale = (double)sp[-args].u.float_number;
+/*fprintf(stderr, "Setting yspacing to %f\n", THIS->yspacing_scale);*/
+  if(THIS->yspacing_scale <= 0.0)
+    THIS->yspacing_scale=0.1;
+  pop_stack();
+}
+
+void font_baseline(INT32 args)
+{
+   pop_n_elems(args);
+   if (THIS)
+      push_int(THIS->baseline);
+   else
+      push_int(0);
+}
+
+void font_set_center(INT32 args)
+{
+  pop_n_elems(args);
+  if(THIS) THIS->justification=J_CENTER;
+}
+
+void font_set_right(INT32 args)
+{
+  pop_n_elems(args);
+  if(THIS) THIS->justification=J_RIGHT;
+}
+
+void font_set_left(INT32 args)
+{
+  pop_n_elems(args);
+  if(THIS) THIS->justification=J_LEFT;
+}
+
+
+/***************** global init etc *****************************/
+
+/*
+
+int load(string filename);  // load font file, true is success
+object write(string text);  // new image object
+int height();               // font heigth
+int baseline();             // font baseline
+
+*/
+
+void init_font_programs(void)
+{
+   start_new_program();
+   add_storage(sizeof(struct font*));
+
+   add_function("load",font_load,
+                "function(string:int)",0);
+
+   add_function("write",font_write,
+                "function(string:object)",0);
+
+   add_function("height",font_height,
+                "function(:int)",0);
+
+   add_function("baseline",font_baseline,
+                "function(:int)",0);
+		
+   add_function("extents",font_text_extents,
+                "function(string ...:array(int))",0);
+		
+   add_function("set_x_spacing",font_set_xspacing_scale,
+                "function(float:void)",0);
+
+   add_function("set_y_spacing",font_set_yspacing_scale,
+                "function(float:void)",0);
+
+   add_function("center", font_set_center, "function(void:void)", 0);
+   add_function("left", font_set_left, "function(void:void)", 0);
+   add_function("right", font_set_right, "function(void:void)", 0);
+
+   
+   set_init_callback(init_font_struct);
+   set_exit_callback(exit_font_struct);
+  
+   font_program=end_program();
+   add_program_constant("font",font_program, 0);
+}
+
+void exit_font(void) 
+{
+  if(font_program)
+  {
+    free_program(font_program);
+    font_program=0;
+  }
+}
+
+
diff --git a/src/modules/Image/image.c b/src/modules/Image/image.c
new file mode 100644
index 0000000000000000000000000000000000000000..3a654b5e22c4c01a22824d9eff8863a461891331
--- /dev/null
+++ b/src/modules/Image/image.c
@@ -0,0 +1,1805 @@
+/* $Id: image.c,v 1.1 1997/02/11 08:35:43 hubbe Exp $ */
+
+#include "global.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "stralloc.h"
+#include "global.h"
+RCSID("$Id: image.c,v 1.1 1997/02/11 08:35:43 hubbe Exp $");
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "threads.h"
+#include "array.h"
+#include "error.h"
+
+#include "image.h"
+#include "builtin_functions.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))
+#define testrange(x) max(min((x),255),0)
+
+#define sq(x) ((x)*(x))
+
+#define CIRCLE_STEPS 128
+static INT32 circle_sin_table[CIRCLE_STEPS];
+#define circle_sin(x) circle_sin_table[((x)+CIRCLE_STEPS)%CIRCLE_STEPS]
+#define circle_cos(x) circle_sin((x)-CIRCLE_STEPS/4)
+
+#define circle_sin_mul(x,y) ((circle_sin(x)*(y))/4096)
+#define circle_cos_mul(x,y) ((circle_cos(x)*(y))/4096)
+
+
+
+#if 0
+#include <sys/resource.h>
+#define CHRONO(X) chrono(X)
+
+static void chrono(char *x)
+{
+   struct rusage r;
+   static struct rusage rold;
+   getrusage(RUSAGE_SELF,&r);
+   fprintf(stderr,"%s: %ld.%06ld - %ld.%06ld\n",x,
+	   r.ru_utime.tv_sec,r.ru_utime.tv_usec,
+
+	   ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?-1:0)
+	   +r.ru_utime.tv_sec-rold.ru_utime.tv_sec,
+           ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?1000000:0)
+           + r.ru_utime.tv_usec-rold.ru_utime.tv_usec
+	   );
+
+   rold=r;
+}
+#else
+#define CHRONO(X)
+#endif
+
+
+/***************** init & exit *********************************/
+
+static int obj_counter=0;
+
+static void init_image_struct(struct object *obj)
+{
+  THIS->img=NULL;
+  THIS->rgb.r=0;
+  THIS->rgb.g=0;
+  THIS->rgb.b=0;
+/*  fprintf(stderr,"init %lx (%d)\n",obj,++obj_counter);*/
+}
+
+static void exit_image_struct(struct object *obj)
+{
+  if (THIS->img) { free(THIS->img); THIS->img=NULL; }
+/*
+  fprintf(stderr,"exit %lx (%d) %dx%d=%.1fKb\n",obj,--obj_counter,
+	  THIS->xsize,THIS->ysize,
+	  (THIS->xsize*THIS->ysize*3+sizeof(struct image))/1024.0);
+	  */
+}
+
+/***************** 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;
+}
+
+static INLINE void getrgbl(rgbl_group *rgb,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);
+   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;
+}
+
+static INLINE void img_line(INT32 x1,INT32 y1,INT32 x2,INT32 y2)
+{   
+   INT32 pixelstep,pos;
+   if (x1==x2) 
+   {
+      if (y1>y2) y1^=y2,y2^=y1,y1^=y2;
+      if (x1<0||x1>=THIS->xsize||
+	  y2<0||y1>=THIS->ysize) return;
+      if (y1<0) y1=0;
+      if (y2>=THIS->ysize) y2=THIS->ysize-1;
+      for (;y1<=y2;y1++) setpixel_test(x1,y1);
+      return;
+   }
+   else if (y1==y2)
+   {
+      if (x1>x2) x1^=x2,x2^=x1,x1^=x2;
+      if (y1<0||y1>=THIS->ysize||
+	  x2<0||x1>=THIS->xsize) return;
+      if (x1<0) x1=0; 
+      if (x2>=THIS->xsize) x2=THIS->xsize-1;
+      for (;x1<=x2;x1++) setpixel_test(x1,y1);
+      return;
+   }
+   else if (abs(x2-x1)<abs(y2-y1)) /* mostly vertical line */
+   {
+      if (y1>y2) y1^=y2,y2^=y1,y1^=y2,
+                 x1^=x2,x2^=x1,x1^=x2;
+      pixelstep=((x2-x1)*1024)/(y2-y1);
+      pos=x1*1024;
+      for (;y1<=y2;y1++)
+      {
+         setpixel_test((pos+512)/1024,y1);
+	 pos+=pixelstep;
+      }
+   }
+   else /* mostly horisontal line */
+   {
+      if (x1>x2) y1^=y2,y2^=y1,y1^=y2,
+                 x1^=x2,x2^=x1,x1^=x2;
+      pixelstep=((y2-y1)*1024)/(x2-x1);
+      pos=y1*1024;
+      for (;x1<=x2;x1++)
+      {
+         setpixel_test(x1,(pos+512)/1024);
+	 pos+=pixelstep;
+      }
+   }
+}
+
+static INLINE rgb_group _pixel_apply_matrix(struct image *img,
+					    int x,int y,
+					    int width,int height,
+					    rgbd_group *matrix,
+					    rgb_group default_rgb,
+					    double div)
+{
+   rgb_group res;
+   int i,j,bx,by,xp,yp;
+   int sumr,sumg,sumb,r,g,b;
+   float qdiv=1.0/div;
+
+   sumr=sumg=sumb=0;
+   r=g=b=0;
+
+   bx=width/2;
+   by=height/2;
+   
+   for (xp=x-bx,i=0; i<width; i++,xp++)
+      for (yp=y-by,j=0; j<height; j++,yp++)
+	 if (xp>=0 && xp<img->xsize && yp>=0 && yp<img->ysize)
+	 {
+	    r+=matrix[i+j*width].r*img->img[xp+yp*img->xsize].r;
+	    g+=matrix[i+j*width].g*img->img[xp+yp*img->xsize].g;
+	    b+=matrix[i+j*width].b*img->img[xp+yp*img->xsize].b;
+#ifdef MATRIX_DEBUG
+	    fprintf(stderr,"%d,%d %d,%d->%d,%d,%d\n",
+		    i,j,xp,yp,
+		    img->img[x+i+(y+j)*img->xsize].r,
+		    img->img[x+i+(y+j)*img->xsize].g,
+		    img->img[x+i+(y+j)*img->xsize].b);
+#endif
+	    sumr+=matrix[i+j*width].r;
+	    sumg+=matrix[i+j*width].g;
+	    sumb+=matrix[i+j*width].b;
+	 }
+   if (sumr) res.r=testrange(default_rgb.r+r/(sumr*div)); 
+   else res.r=testrange(r*qdiv+default_rgb.r);
+   if (sumg) res.g=testrange(default_rgb.g+g/(sumg*div)); 
+   else res.g=testrange(g*qdiv+default_rgb.g);
+   if (sumb) res.b=testrange(default_rgb.g+b/(sumb*div)); 
+   else res.b=testrange(b*qdiv+default_rgb.b);
+#ifdef MATRIX_DEBUG
+   fprintf(stderr,"->%d,%d,%d\n",res.r,res.g,res.b);
+#endif
+   return res;
+}
+
+
+void img_apply_matrix(struct image *dest,
+		      struct image *img,
+		      int width,int height,
+		      rgbd_group *matrix,
+		      rgb_group default_rgb,
+		      double div)
+{
+   rgb_group *d,*ip,*dp;
+   rgbd_group *mp;
+   int i,j,x,y,bx,by,ex,ey,xp,yp;
+   int sumr,sumg,sumb;
+   double qr,qg,qb;
+   register double r=0,g=0,b=0;
+
+THREADS_ALLOW();
+
+   sumr=sumg=sumb=0;
+   for (i=0; i<width; i++)
+      for (j=0; j<height; j++)
+      {
+	 sumr+=matrix[i+j*width].r;
+	 sumg+=matrix[i+j*width].g;
+	 sumb+=matrix[i+j*width].b;
+      }
+
+   if (!sumr) sumr=1; sumr*=div; qr=1.0/sumr;
+   if (!sumg) sumg=1; sumg*=div; qg=1.0/sumg;
+   if (!sumb) sumb=1; sumb*=div; qb=1.0/sumb;
+
+   bx=width/2;
+   by=height/2;
+   ex=width-bx;
+   ey=height-by;
+   
+   d=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1);
+
+   if(!d) error("Out of memory.\n");
+   
+CHRONO("apply_matrix, one");
+
+   for (y=by; y<img->ysize-ey; y++)
+   {
+      dp=d+y*img->xsize+by;
+      for (x=bx; x<img->xsize-ex; x++)
+      {
+	 r=g=b=0;
+	 mp=matrix;
+	 for (yp=y-by,j=0; j<height; j++,yp++)
+	 {
+	    ip=img->img+(x-bx)+yp*img->xsize;
+	    for (i=0; i<width; i++)
+	    {
+	       r+=ip->r*mp->r;
+ 	       g+=ip->g*mp->g;
+ 	       b+=ip->b*mp->b;
+#ifdef MATRIX_DEBUG
+	       fprintf(stderr,"%d,%d ->%d,%d,%d\n",
+		       i,j,
+		       img->img[x+i+(y+j)*img->xsize].r,
+		       img->img[x+i+(y+j)*img->xsize].g,
+		       img->img[x+i+(y+j)*img->xsize].b);
+#endif
+	       mp++;
+	       ip++;
+	    }
+	 }
+#ifdef MATRIX_DEBUG
+	 fprintf(stderr,"->%d,%d,%d\n",r/sumr,g/sumg,b/sumb);
+#endif
+	 r=default_rgb.r+(int)(r*qr+0.5); dp->r=testrange(r);
+	 g=default_rgb.g+(int)(g*qg+0.5); dp->g=testrange(g);
+	 b=default_rgb.b+(int)(b*qb+0.5); dp->b=testrange(b);
+	 dp++;
+      }
+   }
+
+CHRONO("apply_matrix, two");
+
+   for (y=0; y<img->ysize; y++)
+   {
+      for (x=0; x<bx; x++)
+	 d[x+y*img->xsize]=_pixel_apply_matrix(img,x,y,width,height,
+					       matrix,default_rgb,div);
+      for (x=img->xsize-ex; x<img->xsize; x++)
+	 d[x+y*img->xsize]=_pixel_apply_matrix(img,x,y,width,height,
+					       matrix,default_rgb,div);
+   }
+
+   for (x=0; x<img->xsize; x++)
+   {
+      for (y=0; y<by; y++)
+	 d[x+y*img->xsize]=_pixel_apply_matrix(img,x,y,width,height,
+					       matrix,default_rgb,div);
+      for (y=img->ysize-ey; y<img->ysize; y++)
+	 d[x+y*img->xsize]=_pixel_apply_matrix(img,x,y,width,height,
+					       matrix,default_rgb,div);
+   }
+
+CHRONO("apply_matrix, three");
+
+   if (dest->img) free(dest->img);
+   *dest=*img;
+   dest->img=d;
+
+THREADS_DISALLOW();
+}
+
+/***************** methods *************************************/
+
+void image_create(INT32 args)
+{
+   if (args<2) return;
+   if (sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT)
+      error("Illegal arguments to image->clone()\n");
+
+   getrgb(THIS,2,args,"image->create()"); 
+
+   if (THIS->img) free(THIS->img);
+	
+   THIS->xsize=sp[-args].u.integer;
+   THIS->ysize=sp[1-args].u.integer;
+   if (THIS->xsize<0) THIS->xsize=0;
+   if (THIS->ysize<0) THIS->ysize=0;
+
+   THIS->img=malloc(sizeof(rgb_group)*THIS->xsize*THIS->ysize +1);
+   if (!THIS->img)
+     error("out of memory\n");
+
+
+   img_clear(THIS->img,THIS->rgb,THIS->xsize*THIS->ysize);
+   pop_n_elems(args);
+}
+
+void image_clone(INT32 args)
+{
+   struct object *o;
+   struct image *img;
+
+   if (args)
+      if (args<2||
+	  sp[-args].type!=T_INT||
+	  sp[1-args].type!=T_INT)
+	 error("Illegal arguments to image->clone()\n");
+
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+   *img=*THIS;
+
+   if (args)
+   {
+      if(sp[-args].u.integer < 0 ||
+	 sp[1-args].u.integer < 0)
+	 error("Illegal size to image->clone()\n");
+      img->xsize=sp[-args].u.integer;
+      img->ysize=sp[1-args].u.integer;
+   }
+
+   getrgb(img,2,args,"image->clone()"); 
+
+   if (img->xsize<0) img->xsize=1;
+   if (img->ysize<0) img->ysize=1;
+
+   img->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1);
+   if (THIS->img)
+   {
+      if (!img->img)
+      {
+	 free_object(o);
+	 error("out of memory\n");
+      }
+      if (img->xsize==THIS->xsize 
+	  && img->ysize==THIS->ysize)
+	 MEMCPY(img->img,THIS->img,sizeof(rgb_group)*img->xsize*img->ysize);
+      else
+	 img_crop(img,THIS,
+		  0,0,img->xsize-1,img->ysize-1);
+	 
+   }
+   else
+      img_clear(img->img,img->rgb,img->xsize*img->ysize);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_clear(INT32 args)
+{
+   struct object *o;
+   struct image *img;
+
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+   *img=*THIS;
+
+   getrgb(img,0,args,"image->clear()"); 
+
+   img->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1);
+   if (!img->img)
+   {
+     free_object(o);
+     error("out of memory\n");
+   }
+
+   img_clear(img->img,img->rgb,img->xsize*img->ysize);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_copy(INT32 args)
+{
+   struct object *o;
+   struct image *img;
+
+   if (!args)
+   {
+      o=clone(image_program,0);
+      if (THIS->img) img_clone((struct image*)o->storage,THIS);
+      pop_n_elems(args);
+      push_object(o);
+      return;
+   }
+   if (args<4||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT||
+       sp[2-args].type!=T_INT||
+       sp[3-args].type!=T_INT)
+      error("illegal arguments to image->copy()\n");
+
+   if (!THIS->img) error("no image\n");
+
+   getrgb(THIS,2,args,"image->crop()"); 
+
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+
+   img_crop(img,THIS,
+	    sp[-args].u.integer,sp[1-args].u.integer,
+	    sp[2-args].u.integer,sp[3-args].u.integer);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+static void image_change_color(INT32 args)
+
+{
+   /* ->change_color([int from-r,g,b,] int to-r,g,b); */
+   rgb_group from,to,*s,*d;
+   INT32 left;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+   if (args<3) error("too few arguments to image->change_color()\n");
+
+   if (args<6)
+   {
+      to=THIS->rgb;   
+      getrgb(THIS,0,args,"image->change_color()");
+      from=THIS->rgb;
+   }
+   else
+   {
+      getrgb(THIS,0,args,"image->change_color()");
+      from=THIS->rgb;
+      getrgb(THIS,3,args,"image->change_color()");
+      to=THIS->rgb;
+   }
+   
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+   *img=*THIS;
+
+   if (!(img->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize +1)))
+   {
+      free_object(o);
+      error("out of memory\n");
+   }
+
+   left=THIS->xsize*THIS->ysize;
+   s=THIS->img;
+   d=img->img;
+   while (left--)
+   {
+      if (s->r==from.r && s->g==from.g && s->b==from.b)
+         *d=to;
+      else
+         *d=*s;
+      d++; s++;
+   }
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+static INLINE int try_autocrop_vertical(INT32 x,INT32 y,INT32 y2,
+					INT32 rgb_set,rgb_group *rgb)
+{
+   if (!rgb_set) *rgb=pixel(THIS,x,y);
+   for (;y<=y2; y++)
+      if (pixel(THIS,x,y).r!=rgb->r ||
+	  pixel(THIS,x,y).g!=rgb->g ||
+	  pixel(THIS,x,y).b!=rgb->b) return 0;
+   return 1;
+}
+
+static INLINE int try_autocrop_horisontal(INT32 y,INT32 x,INT32 x2,
+					  INT32 rgb_set,rgb_group *rgb)
+{
+   if (!rgb_set) *rgb=pixel(THIS,x,y);
+   for (;x<=x2; x++)
+      if (pixel(THIS,x,y).r!=rgb->r ||
+	  pixel(THIS,x,y).g!=rgb->g ||
+	  pixel(THIS,x,y).b!=rgb->b) return 0;
+   return 1;
+}
+
+void image_autocrop(INT32 args)
+{
+   INT32 border=0,x1,y1,x2,y2;
+   rgb_group rgb;
+   int rgb_set=0,done;
+   struct object *o;
+   struct image *img;
+   int left=1,right=1,top=1,bottom=1;
+
+   if (args)
+      if (sp[-args].type!=T_INT)
+         error("Illegal argument to image->autocrop()\n");
+      else
+         border=sp[-args].u.integer; 
+
+   if (args>=5)
+   {
+      left=!(sp[1-args].type==T_INT && sp[1-args].u.integer==0);
+      right=!(sp[2-args].type==T_INT && sp[2-args].u.integer==0);
+      top=!(sp[3-args].type==T_INT && sp[3-args].u.integer==0);
+      bottom=!(sp[4-args].type==T_INT && sp[4-args].u.integer==0);
+      getrgb(THIS,5,args,"image->autocrop()"); 
+   }
+   else getrgb(THIS,1,args,"image->autocrop()"); 
+
+   if (!THIS->img)
+   {
+      error("no image\n");
+      return;
+   }
+
+   x1=y1=0;
+   x2=THIS->xsize-1;
+   y2=THIS->ysize-1;
+
+   while (x2>x1 && y2>y1)
+   {
+      done=0;
+      if (left &&
+	  try_autocrop_vertical(x1,y1,y2,rgb_set,&rgb)) x1++,done=rgb_set=1;
+      if (right &&
+	  x2>x1 && 
+	  try_autocrop_vertical(x2,y1,y2,rgb_set,&rgb)) x2--,done=rgb_set=1;
+      if (top &&
+	  try_autocrop_horisontal(y1,x1,x2,rgb_set,&rgb)) y1++,done=rgb_set=1;
+      if (bottom &&
+	  y2>y1 && 
+	  try_autocrop_horisontal(y2,x1,x2,rgb_set,&rgb)) y2--,done=rgb_set=1;
+      if (!done) break;
+   }
+
+   o=clone(image_program,0);
+   img=(struct image*)(o->storage);
+
+   img_crop(img,THIS,x1-border,y1-border,x2+border,y2+border);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+
+void image_setcolor(INT32 args)
+{
+   if (args<3)
+      error("illegal arguments to image->setcolor()\n");
+   getrgb(THIS,0,args,"image->setcolor()");
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_setpixel(INT32 args)
+{
+   INT32 x,y;
+   if (args<2||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT)
+      error("Illegal arguments to image->setpixel()\n");
+   getrgb(THIS,2,args,"image->setpixel()");   
+   if (!THIS->img) return;
+   x=sp[-args].u.integer;
+   y=sp[1-args].u.integer;
+   if (!THIS->img) return;
+   setpixel_test(x,y);
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_getpixel(INT32 args)
+{
+   INT32 x,y;
+   rgb_group rgb;
+
+   if (args<2||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT)
+      error("Illegal arguments to image->getpixel()\n");
+
+   if (!THIS->img) error("No image.\n");
+
+   x=sp[-args].u.integer;
+   y=sp[1-args].u.integer;
+   if (!THIS->img) return;
+   if(x<0||y<0||x>=THIS->xsize||y>=THIS->ysize)
+      rgb=THIS->rgb;
+   else
+      rgb=pixel(THIS,x,y);
+
+   pop_n_elems(args);
+   push_int(rgb.r);
+   push_int(rgb.g);
+   push_int(rgb.b);
+   f_aggregate(3);
+}
+
+void image_line(INT32 args)
+{
+   if (args<4||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT||
+       sp[2-args].type!=T_INT||
+       sp[3-args].type!=T_INT)
+      error("Illegal arguments to image->line()\n");
+   getrgb(THIS,4,args,"image->line()");
+   if (!THIS->img) return;
+
+   img_line(sp[-args].u.integer,
+	    sp[1-args].u.integer,
+	    sp[2-args].u.integer,
+	    sp[3-args].u.integer);
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_box(INT32 args)
+{
+   if (args<4||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT||
+       sp[2-args].type!=T_INT||
+       sp[3-args].type!=T_INT)
+      error("Illegal arguments to image->box()\n");
+   getrgb(THIS,4,args,"image->box()");
+   if (!THIS->img) return;
+
+   img_box(sp[-args].u.integer,
+	   sp[1-args].u.integer,
+	   sp[2-args].u.integer,
+	   sp[3-args].u.integer);
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_circle(INT32 args)
+{
+   INT32 x,y,rx,ry;
+   INT32 i;
+
+   if (args<4||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT||
+       sp[2-args].type!=T_INT||
+       sp[3-args].type!=T_INT)
+      error("illegal arguments to image->circle()\n");
+   getrgb(THIS,4,args,"image->circle()");
+   if (!THIS->img) return;
+
+   x=sp[-args].u.integer;
+   y=sp[1-args].u.integer;
+   rx=sp[2-args].u.integer;
+   ry=sp[3-args].u.integer;
+   
+   for (i=0; i<CIRCLE_STEPS; i++)
+      img_line(x+circle_sin_mul(i,rx),
+	       y+circle_cos_mul(i,ry),
+	       x+circle_sin_mul(i+1,rx),
+	       y+circle_cos_mul(i+1,ry));
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+static INLINE void get_rgba_group_from_array_index(rgba_group *rgba,struct array *v,INT32 index)
+{
+   struct svalue s,s2;
+   array_index_no_free(&s,v,index);
+   if (s.type!=T_ARRAY||
+       s.u.array->size<3) 
+      rgba->r=rgba->b=rgba->g=rgba->alpha=0; 
+   else
+   {
+      array_index_no_free(&s2,s.u.array,0);
+      if (s2.type!=T_INT) rgba->r=0; else rgba->r=s2.u.integer;
+      array_index(&s2,s.u.array,1);
+      if (s2.type!=T_INT) rgba->g=0; else rgba->g=s2.u.integer;
+      array_index(&s2,s.u.array,2);
+      if (s2.type!=T_INT) rgba->b=0; else rgba->b=s2.u.integer;
+      if (s.u.array->size>=4)
+      {
+	 array_index(&s2,s.u.array,3);
+	 if (s2.type!=T_INT) rgba->alpha=0; else rgba->alpha=s2.u.integer;
+      }
+      else rgba->alpha=0;
+      free_svalue(&s2);
+   }
+   free_svalue(&s); 
+   return; 
+}
+
+static INLINE void
+   add_to_rgba_sum_with_factor(rgba_group *sum,
+			       rgba_group rgba,
+			       float factor)
+{
+   sum->r=testrange(sum->r+(INT32)(rgba.r*factor+0.5));
+   sum->g=testrange(sum->g+(INT32)(rgba.g*factor+0.5));
+   sum->b=testrange(sum->b+(INT32)(rgba.b*factor+0.5));
+   sum->alpha=testrange(sum->alpha+(INT32)(rgba.alpha*factor+0.5));
+}
+
+static INLINE void
+   add_to_rgb_sum_with_factor(rgb_group *sum,
+			      rgba_group rgba,
+			      float factor)
+{
+   sum->r=testrange(sum->r+(INT32)(rgba.r*factor+0.5));
+   sum->g=testrange(sum->g+(INT32)(rgba.g*factor+0.5));
+   sum->b=testrange(sum->b+(INT32)(rgba.b*factor+0.5));
+}
+
+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;
+   struct image *this;
+
+   if (args<5||
+       sp[-args].type!=T_INT||
+       sp[1-args].type!=T_INT||
+       sp[2-args].type!=T_INT||
+       sp[3-args].type!=T_INT||
+       sp[4-args].type!=T_ARRAY||
+       sp[4-args].u.array->size<4)
+      error("Illegal number of arguments to image->tuned_box()\n");
+
+   if (!THIS->img)
+      error("no image\n");
+
+   x1=sp[-args].u.integer;
+   y1=sp[1-args].u.integer;
+   x2=sp[2-args].u.integer;
+   y2=sp[3-args].u.integer;
+
+   get_rgba_group_from_array_index(&topleft,sp[4-args].u.array,0);
+   get_rgba_group_from_array_index(&topright,sp[4-args].u.array,1);
+   get_rgba_group_from_array_index(&bottomleft,sp[4-args].u.array,2);
+   get_rgba_group_from_array_index(&bottomright,sp[4-args].u.array,3);
+
+   if (x1>x2) x1^=x2,x2^=x1,x1^=x2,
+              sum=topleft,topleft=topright,topright=sum,
+              sum=bottomleft,bottomleft=bottomright,bottomright=sum;
+   if (y1>y2) y1^=y2,y2^=y1,y1^=y2,
+              sum=topleft,topleft=bottomleft,bottomleft=sum,
+              sum=topright,topright=bottomright,bottomright=sum;
+   if (x2<0||y2<0||x1>=THIS->xsize||y1>=THIS->ysize) return;
+
+   xw=x2-x1;
+   yw=y2-y1;
+
+   this=THIS;
+   THREADS_ALLOW();
+
+   for (x=max(0,-x1); x<=xw && x+x1<THIS->xsize; x++)
+   {
+#define tune_factor(a,aw) (1.0-((float)(a)/(aw)))
+      INT32 ymax;
+      float tfx1=tune_factor(x,xw);
+      float tfx2=tune_factor(xw-x,xw);
+
+      ymax=min(yw,this->ysize-y1);
+      img=this->img+x+x1+this->xsize*max(0,y1);
+      if (topleft.alpha||topright.alpha||bottomleft.alpha||bottomright.alpha)
+	 for (y=max(0,-y1); y<ymax; y++)
+	 {
+	    float tfy;
+	    sum=sumzero;
+
+	    add_to_rgba_sum_with_factor(&sum,topleft,(tfy=tune_factor(y,yw))*tfx1);
+	    add_to_rgba_sum_with_factor(&sum,topright,tfy*tfx2);
+	    add_to_rgba_sum_with_factor(&sum,bottomleft,(tfy=tune_factor(yw-y,yw))*tfx1);
+	    add_to_rgba_sum_with_factor(&sum,bottomright,tfy*tfx2);
+
+	    set_rgb_group_alpha(*img, sum,sum.alpha);
+	    img+=this->xsize;
+	 }
+      else
+	 for (y=max(0,-y1); y<ymax; y++)
+	 {
+	    float tfy;
+	    rgb_group sum={0,0,0};
+
+	    add_to_rgb_sum_with_factor(&sum,topleft,(tfy=tune_factor(y,yw))*tfx1);
+	    add_to_rgb_sum_with_factor(&sum,topright,tfy*tfx2);
+	    add_to_rgb_sum_with_factor(&sum,bottomleft,(tfy=tune_factor(yw-y,yw))*tfx1);
+	    add_to_rgb_sum_with_factor(&sum,bottomright,tfy*tfx2);
+
+	    *img=sum;
+	    img+=this->xsize;
+	 }
+	 
+   }
+   THREADS_DISALLOW();
+
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+void image_xsize(INT32 args)
+{
+   pop_n_elems(args);
+   if (THIS->img) push_int(THIS->xsize); else push_int(0);
+}
+
+void image_ysize(INT32 args)
+{
+   pop_n_elems(args);
+   if (THIS->img) push_int(THIS->ysize); else push_int(0);
+}
+
+void image_gray(INT32 args)
+{
+   INT32 x,y,div;
+   rgbl_group rgb;
+   rgb_group *d,*s;
+   struct object *o;
+   struct image *img;
+
+   if (args<3)
+   {
+      rgb.r=87;
+      rgb.g=127;
+      rgb.b=41;
+   }
+   else
+      getrgbl(&rgb,0,args,"image->gray()");
+   div=rgb.r+rgb.g+rgb.b;
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+   x=THIS->xsize*THIS->ysize;
+   THREADS_ALLOW();
+   while (x--)
+   {
+      d->r=d->g=d->b=
+	 testrange( ((((long)s->r)*rgb.r+
+		      ((long)s->g)*rgb.g+
+		      ((long)s->b)*rgb.b)/div) );
+      d++;
+      s++;
+   }
+   THREADS_DISALLOW();
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_color(INT32 args)
+{
+   INT32 x,y;
+   rgbl_group rgb;
+   rgb_group *s,*d;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+   if (args<3)
+   {
+      if (args>0 && sp[-args].type==T_INT)
+	 rgb.r=rgb.b=rgb.g=sp[-args].u.integer;
+      else 
+	 rgb.r=THIS->rgb.r,
+	 rgb.g=THIS->rgb.g,
+	 rgb.b=THIS->rgb.b;
+   }
+   else
+      getrgbl(&rgb,0,args,"image->color()");
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+
+   x=THIS->xsize*THIS->ysize;
+
+   THREADS_ALLOW();
+   while (x--)
+   {
+      d->r=testrange( (((long)rgb.r*s->r)/255) );
+      d->g=testrange( (((long)rgb.g*s->g)/255) );
+      d->b=testrange( (((long)rgb.b*s->b)/255) );
+      d++;
+      s++;
+   }
+   THREADS_DISALLOW();
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_invert(INT32 args)
+{
+   INT32 x,y;
+   rgb_group *s,*d;
+   struct object *o;
+   struct image *img;
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+
+   x=THIS->xsize*THIS->ysize;
+   THREADS_ALLOW();
+   while (x--)
+   {
+      d->r=testrange( 255-s->r );
+      d->g=testrange( 255-s->g );
+      d->b=testrange( 255-s->b );
+      d++;
+      s++;
+   }
+   THREADS_DISALLOW();
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_threshold(INT32 args)
+{
+   INT32 x,y;
+   rgb_group *s,*d,rgb;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+
+   getrgb(THIS,0,args,"image->threshold()");
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+   rgb=THIS->rgb;
+
+   x=THIS->xsize*THIS->ysize;
+   THREADS_ALLOW();
+   while (x--)
+   {
+      if (s->r>=rgb.r &&
+	  s->g>=rgb.g &&
+	  s->b>=rgb.b)
+	 d->r=d->g=d->b=255;
+      else
+	 d->r=d->g=d->b=0;
+
+      d++;
+      s++;
+   }
+   THREADS_DISALLOW();
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_distancesq(INT32 args)
+{
+   INT32 i;
+   rgb_group *s,*d,rgb;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+
+   getrgb(THIS,0,args,"image->threshold()");
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+   rgb=THIS->rgb;
+
+   THREADS_ALLOW();
+   i=img->xsize*img->ysize;
+   while (i--)
+   {
+#define DISTANCE(A,B) \
+   (sq((long)(A).r-(B).r)+sq((long)(A).g-(B).g)+sq((long)(A).b-(B).b))
+      d->r=d->g=d->b=testrange(DISTANCE(*s,rgb)>>8);
+      d++; s++;
+   }
+   THREADS_DISALLOW();
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+/*#define DEBUG_ISF*/
+#define ISF_LEFT 4
+#define ISF_RIGHT 8
+
+void isf_seek(int mode,int ydir,INT32 low_limit,INT32 x1,INT32 x2,INT32 y,
+	      rgb_group *src,rgb_group *dest,INT32 xsize,INT32 ysize,
+	      rgb_group rgb,int reclvl)
+{
+   INT32 x,xr;
+   INT32 j;
+
+#ifdef DEBUG_ISF
+   fprintf(stderr,"isf_seek reclvl=%d mode=%d, ydir=%d, low_limit=%d, x1=%d, x2=%d, y=%d, src=%lx, dest=%lx, xsize=%d, ysize=%d, rgb=%d,%d,%d\n",reclvl,
+	   mode,ydir,low_limit,x1,x2,y,src,dest,xsize,ysize,rgb.r,rgb.g,rgb.b);
+#endif
+
+#define MARK_DISTANCE(_dest,_value) \
+      ((_dest).r=(_dest).g=(_dest).b=(max(1,255-(_value>>8))))
+   if ( mode&ISF_LEFT ) /* scan left from x1 */
+   {
+      x=x1;
+      while (x>0)
+      {
+	 x--;
+#ifdef DEBUG_ISF
+fprintf(stderr,"<-- %d (",DISTANCE(rgb,src[x+y*xsize]));
+fprintf(stderr," %d,%d,%d)\n",src[x+y*xsize].r,src[x+y*xsize].g,src[x+y*xsize].b);
+#endif
+	 if ( (j=DISTANCE(rgb,src[x+y*xsize])) >low_limit)
+	 {
+	    x++;
+	    break;
+	 }
+	 else
+	 {
+	    if (dest[x+y*xsize].r) { x++; break; } /* been there */
+	    MARK_DISTANCE(dest[x+y*xsize],j);
+	 }
+      }
+      if (x1>x)
+      {
+	 isf_seek(ISF_LEFT,-ydir,low_limit,
+		  x,x1-1,y,src,dest,xsize,ysize,rgb,reclvl+1);
+      }
+      x1=x;
+   }
+   if ( mode&ISF_RIGHT ) /* scan right from x2 */
+   {
+      x=x2;
+      while (x<xsize-1)
+      {
+	 x++;
+#ifdef DEBUG_ISF
+fprintf(stderr,"--> %d (",DISTANCE(rgb,src[x+y*xsize]));
+fprintf(stderr," %d,%d,%d)\n",src[x+y*xsize].r,src[x+y*xsize].g,src[x+y*xsize].b);
+#endif
+	 if ( (j=DISTANCE(rgb,src[x+y*xsize])) >low_limit)
+	 {
+	    x--;
+	    break;
+	 }
+	 else
+	 {
+	    if (dest[x+y*xsize].r) { x--; break; } /* done that */
+	    MARK_DISTANCE(dest[x+y*xsize],j);
+	 }
+      }
+      if (x2<x)
+      {
+	 isf_seek(ISF_RIGHT,-ydir,low_limit,
+		  x2+1,x,y,src,dest,xsize,ysize,rgb,reclvl+1);
+      }
+      x2=x;
+   }
+   xr=x=x1;
+   y+=ydir;
+   if (y<0 || y>=ysize) return;
+   while (x<=x2)
+   {
+#ifdef DEBUG_ISF
+fprintf(stderr,"==> %d (",DISTANCE(rgb,src[x+y*xsize]));
+fprintf(stderr," %d,%d,%d)\n",src[x+y*xsize].r,src[x+y*xsize].g,src[x+y*xsize].b);
+#endif
+      if ( dest[x+y*xsize].r || /* seen that */
+	   (j=DISTANCE(rgb,src[x+y*xsize])) >low_limit) 
+      {
+	 if (xr<x)
+	    isf_seek(ISF_LEFT*(xr==x1),ydir,low_limit,
+		     xr,x-1,y,src,dest,xsize,ysize,rgb,reclvl+1);
+	 while (++x<=x2)
+	    if ( (j=DISTANCE(rgb,src[x+y*xsize])) <=low_limit) break;
+	 xr=x;
+/*	 x++; hokuspokus /law */
+/*       n�n dag ska jag f�rs�ka begripa varf�r... */
+	 if (x>x2) return;
+	 continue;
+      }
+      MARK_DISTANCE(dest[x+y*xsize],j);
+      x++;
+   }
+   if (x>xr)
+      isf_seek((ISF_LEFT*(xr==x1))|ISF_RIGHT,ydir,low_limit,
+	       xr,x-1,y,src,dest,xsize,ysize,rgb,reclvl+1);
+}
+
+void image_select_from(INT32 args)
+{
+   struct object *o;
+   struct image *img;
+   INT32 low_limit=0;
+
+   if (!THIS->img) error("no image\n");
+
+   if (args<2 
+       || sp[-args].type!=T_INT
+       || sp[1-args].type!=T_INT)
+      error("Illegal argument(s) to image->select_from()\n");
+
+   if (args>=3)
+      if (sp[2-args].type!=T_INT)
+	 error("Illegal argument 3 (edge type) to image->select_from()\n");
+      else
+	 low_limit=max(0,sp[2-args].u.integer);
+   else
+      low_limit=30;
+   low_limit=low_limit*low_limit;
+
+   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");
+   }
+   MEMSET(img->img,0,sizeof(rgb_group)*img->xsize*img->ysize);
+
+   if (sp[-args].u.integer>=0 && sp[-args].u.integer<img->xsize 
+       && sp[1-args].u.integer>=0 && sp[1-args].u.integer<img->ysize)
+   {
+      isf_seek(ISF_LEFT|ISF_RIGHT,1,low_limit,
+	       sp[-args].u.integer,sp[-args].u.integer,
+	       sp[1-args].u.integer,
+	       THIS->img,img->img,img->xsize,img->ysize,
+	       pixel(THIS,sp[-args].u.integer,sp[1-args].u.integer),0);
+      isf_seek(ISF_LEFT|ISF_RIGHT,-1,low_limit,
+	       sp[-args].u.integer,sp[-args].u.integer,
+	       sp[1-args].u.integer,
+	       THIS->img,img->img,img->xsize,img->ysize,
+	       pixel(THIS,sp[-args].u.integer,sp[1-args].u.integer),0);
+   }
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_apply_matrix(INT32 args)
+{
+   int width,height,i,j;
+   rgbd_group *matrix;
+   rgb_group default_rgb;
+   struct object *o;
+   double div;
+
+CHRONO("apply_matrix");
+
+   if (args<1 ||
+       sp[-args].type!=T_ARRAY)
+      error("Illegal arguments to image->apply_matrix()\n");
+
+   if (args>3)
+      if (sp[1-args].type!=T_INT ||
+	  sp[2-args].type!=T_INT ||
+	  sp[3-args].type!=T_INT)
+	 error("Illegal argument(s) (2,3,4) to image->apply_matrix()\n");
+      else
+      {
+	 default_rgb.r=sp[1-args].u.integer;
+	 default_rgb.g=sp[1-args].u.integer;
+	 default_rgb.b=sp[1-args].u.integer;
+      }
+   else
+   {
+      default_rgb.r=0;
+      default_rgb.g=0;
+      default_rgb.b=0;
+   }
+
+   if (args>4 
+       && sp[4-args].type==T_INT)
+   {
+      div=sp[4-args].u.integer;
+      if (!div) div=1;
+   }
+   else if (args>4 
+	    && sp[4-args].type==T_FLOAT)
+   {
+      div=sp[4-args].u.float_number;
+      if (!div) div=1;
+   }
+   else div=1;
+   
+   height=sp[-args].u.array->size;
+   width=-1;
+   for (i=0; i<height; i++)
+   {
+      struct svalue s;
+      array_index_no_free(&s,sp[-args].u.array,i);
+      if (s.type!=T_ARRAY) 
+	 error("Illegal contents of (root) array (image->apply_matrix)\n");
+      if (width==-1)
+	 width=s.u.array->size;
+      else
+	 if (width!=s.u.array->size)
+	    error("Arrays has different size (image->apply_matrix)\n");
+      free_svalue(&s);
+   }
+   if (width==-1) width=0;
+
+   matrix=malloc(sizeof(rgbd_group)*width*height+1);
+   if (!matrix) error("Out of memory");
+   
+   for (i=0; i<height; i++)
+   {
+      struct svalue s,s2;
+      array_index_no_free(&s,sp[-args].u.array,i);
+      for (j=0; j<width; j++)
+      {
+	 array_index_no_free(&s2,s.u.array,j);
+	 if (s2.type==T_ARRAY && s2.u.array->size == 3)
+	 {
+	    struct svalue s3;
+	    array_index_no_free(&s3,s2.u.array,0);
+	    if (s3.type==T_INT) matrix[j+i*width].r=s3.u.integer; 
+	    else matrix[j+i*width].r=0;
+	    free_svalue(&s3);
+	    array_index_no_free(&s3,s2.u.array,1);
+	    if (s3.type==T_INT) matrix[j+i*width].g=s3.u.integer;
+	    else matrix[j+i*width].g=0;
+	    free_svalue(&s3);
+	    array_index_no_free(&s3,s2.u.array,2);
+	    if (s3.type==T_INT) matrix[j+i*width].b=s3.u.integer; 
+	    else matrix[j+i*width].b=0;
+	    free_svalue(&s3);
+	 }
+	 else if (s2.type==T_INT)
+	    matrix[j+i*width].r=matrix[j+i*width].g=
+	       matrix[j+i*width].b=s2.u.integer;
+	 else
+	    matrix[j+i*width].r=matrix[j+i*width].g=
+	       matrix[j+i*width].b=0;
+	 free_svalue(&s2);
+      }
+      free_svalue(&s2);
+   }
+
+   o=clone(image_program,0);
+
+CHRONO("apply_matrix, begin");
+
+   if (THIS->img)
+      img_apply_matrix((struct image*)o->storage,THIS,
+		       width,height,matrix,default_rgb,div);
+
+CHRONO("apply_matrix, end");
+
+   free(matrix);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_modify_by_intensity(INT32 args)
+{
+   INT32 x,y,i;
+   rgbl_group rgb;
+   rgb_group *list;
+   rgb_group *s,*d;
+   struct object *o;
+   struct image *img;
+   long div;
+
+   if (!THIS->img) error("no image\n");
+   if (args<5) 
+      error("too few arguments to image->modify_by_intensity()\n");
+   
+   getrgbl(&rgb,0,args,"image->modify_by_intensity()");
+   div=rgb.r+rgb.g+rgb.b;
+   if (!div) div=1;
+
+   s=malloc(sizeof(rgb_group)*(args-3)+1);
+   if (!s) error("Out of memory\n");
+
+   for (x=0; x<args-3; x++)
+   {
+      if (sp[3-args+x].type==T_INT)
+	 s[x].r=s[x].g=s[x].b=testrange( sp[3-args+x].u.integer );
+      else if (sp[3-args+x].type==T_ARRAY &&
+	       sp[3-args+x].u.array->size >= 3)
+      {
+	 struct svalue sv;
+	 array_index_no_free(&sv,sp[3-args+x].u.array,0);
+	 if (sv.type==T_INT) s[x].r=testrange( sv.u.integer );
+	 else s[x].r=0;
+	 array_index(&sv,sp[3-args+x].u.array,1);
+	 if (sv.type==T_INT) s[x].g=testrange( sv.u.integer );
+	 else s[x].g=0;
+	 array_index(&sv,sp[3-args+x].u.array,2);
+	 if (sv.type==T_INT) s[x].b=testrange( sv.u.integer );
+	 else s[x].b=0;
+	 free_svalue(&sv);
+      }
+      else s[x].r=s[x].g=s[x].b=0;
+   }
+
+   list=malloc(sizeof(rgb_group)*256+1);
+   if (!list) 
+   {
+      free(s);
+      error("out of memory\n");
+   }
+   for (x=0; x<args-4; x++)
+   {
+      INT32 p1,p2,r;
+      p1=(255L*x)/(args-4);
+      p2=(255L*(x+1))/(args-4);
+      r=p2-p1;
+      for (y=0; y<r; y++)
+      {
+	 list[y+p1].r=(((long)s[x].r)*(r-y)+((long)s[x+1].r)*(y))/r;
+	 list[y+p1].g=(((long)s[x].g)*(r-y)+((long)s[x+1].g)*(y))/r;
+	 list[y+p1].b=(((long)s[x].b)*(r-y)+((long)s[x+1].b)*(y))/r;
+      }
+   }
+   list[255]=s[x];
+   free(s);
+
+   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");
+   }
+
+   d=img->img;
+   s=THIS->img;
+
+
+   x=THIS->xsize*THIS->ysize;
+   THREADS_ALLOW();
+   while (x--)
+   {
+      i= testrange( ((((long)s->r)*rgb.r+
+		      ((long)s->g)*rgb.g+
+		      ((long)s->b)*rgb.b)/div) );
+      *d=list[i];
+      d++;
+      s++;
+   }
+   THREADS_DISALLOW();
+
+   free(list);
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+static void image_map_closest(INT32 args)
+{
+   struct colortable *ct;
+   long i;
+   rgb_group *d,*s;
+   struct object *o;
+
+   if (!THIS->img) error("no image\n");
+   if (args<1
+       || sp[-args].type!=T_ARRAY)
+      error("illegal argument to image->map_closest()\n");
+
+   push_int(THIS->xsize);
+   push_int(THIS->ysize);
+   o=clone(image_program,2);
+      
+   ct=colortable_from_array(sp[-args].u.array,"image->map_closest()\n");
+   pop_n_elems(args);
+
+   i=THIS->xsize*THIS->ysize;
+   s=THIS->img;
+   d=((struct image*)(o->storage))->img;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      *d=ct->clut[colortable_rgb_nearest(ct,*s)];
+      d++; *s++;
+   }
+   THREADS_DISALLOW();
+
+   colortable_free(ct);
+   push_object(o);
+}
+
+static void image_map_fast(INT32 args)
+{
+   struct colortable *ct;
+   long i;
+   rgb_group *d,*s;
+   struct object *o;
+
+   if (!THIS->img) error("no image\n");
+   if (args<1
+       || sp[-args].type!=T_ARRAY)
+      error("illegal argument to image->map_closest()\n");
+
+   push_int(THIS->xsize);
+   push_int(THIS->ysize);
+   o=clone(image_program,2);
+      
+   ct=colortable_from_array(sp[-args].u.array,"image->map_closest()\n");
+   pop_n_elems(args);
+
+   i=THIS->xsize*THIS->ysize;
+   s=THIS->img;
+   d=((struct image*)(o->storage))->img;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      *d=ct->clut[colortable_rgb(ct,*s)];
+      d++; *s++;
+   }
+   THREADS_DISALLOW();
+
+   colortable_free(ct);
+   push_object(o);
+}
+
+static void image_map_fs(INT32 args)
+{
+   struct colortable *ct;
+   INT32 i,j,xs;
+   rgb_group *d,*s;
+   struct object *o;
+   int *res,w;
+   rgbl_group *errb;
+   
+   if (!THIS->img) error("no image\n");
+   if (args<1
+       || sp[-args].type!=T_ARRAY)
+      error("illegal argument to image->map_fs()\n");
+
+   push_int(THIS->xsize);
+   push_int(THIS->ysize);
+   o=clone(image_program,2);
+      
+   res=(int*)xalloc(sizeof(int)*THIS->xsize);
+   errb=(rgbl_group*)xalloc(sizeof(rgbl_group)*THIS->xsize);
+      
+   ct=colortable_from_array(sp[-args].u.array,"image->map_closest()\n");
+   pop_n_elems(args);
+
+   for (i=0; i<THIS->xsize; i++)
+      errb[i].r=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+      errb[i].g=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+      errb[i].b=(rand()%(FS_SCALE*2+1))-FS_SCALE;
+
+   i=THIS->ysize;
+   s=THIS->img;
+   d=((struct image*)(o->storage))->img;
+   w=0;
+   xs=THIS->xsize;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      image_floyd_steinberg(s,xs,errb,w=!w,res,ct);
+      for (j=0; j<THIS->xsize; j++)
+	 *(d++)=ct->clut[res[j]];
+      s+=xs;
+   }
+   THREADS_DISALLOW();
+
+   free(errb);
+   free(res);
+   colortable_free(ct);
+   push_object(o);
+}
+
+void image_select_colors(INT32 args)
+{
+   struct colortable *ct;
+   int colors,i;
+
+   if (args<1
+      || sp[-args].type!=T_INT)
+      error("Illegal argument to image->select_colors()\n");
+
+   colors=sp[-args].u.integer;
+   pop_n_elems(args);
+   if (!THIS->img) { error("no image\n");  return; }
+
+   ct=colortable_quant(THIS,colors);
+   for (i=0; i<colors; i++)
+   {
+      push_int(ct->clut[i].r);
+      push_int(ct->clut[i].g);
+      push_int(ct->clut[i].b);
+      f_aggregate(3);
+   }
+   f_aggregate(colors);
+   colortable_free(ct);
+}
+
+/***************** global init etc *****************************/
+
+#define RGB_TYPE "int|void,int|void,int|void,int|void"
+
+void init_font_programs(void);
+void exit_font(void);
+
+void pike_module_init()
+{
+   int i;
+
+   image_noise_init();
+
+   start_new_program();
+   add_storage(sizeof(struct image));
+
+   add_function("create",image_create,
+		"function(int,int,"RGB_TYPE":void)",0);
+   add_function("clone",image_clone,
+		"function(int,int,"RGB_TYPE":object)",0);
+   add_function("new",image_clone, /* alias */
+		"function(int,int,"RGB_TYPE":object)",0);
+   add_function("clear",image_clear,
+		"function("RGB_TYPE":object)",0);
+   add_function("toppm",image_toppm,
+		"function(:string)",0);
+   add_function("frompnm",image_frompnm,
+		"function(string:object|string)",0);
+   add_function("fromppm",image_frompnm,
+		"function(string:object|string)",0);
+   add_function("fromgif",image_fromgif,
+		"function(string:object)",0);
+   add_function("togif",image_togif,
+		"function(:string)",0);
+   add_function("togif_fs",image_togif_fs,
+		"function(:string)",0);
+   add_function("gif_begin",image_gif_begin,
+		"function(int:string)",0);
+   add_function("gif_add",image_gif_add,
+		"function(int|void,int|void:string)",0);
+   add_function("gif_add_fs",image_gif_add_fs,
+		"function(int|void,int|void:string)",0);
+   add_function("gif_add_nomap",image_gif_add_nomap,
+		"function(int|void,int|void:string)",0);
+   add_function("gif_add_fs_nomap",image_gif_add_fs_nomap,
+		"function(int|void,int|void:string)",0);
+   add_function("gif_end",image_gif_end,
+		"function(:string)",0);
+   add_function("gif_netscape_loop",image_gif_netscape_loop,
+		"function(:string)",0);
+
+   add_function("copy",image_copy,
+		"function(void|int,void|int,void|int,void|int,"RGB_TYPE":object)",0);
+   add_function("autocrop",image_autocrop,
+		"function(void|int ...:object)",0);
+   add_function("scale",image_scale,
+		"function(int|float,int|float|void:object)",0);
+   add_function("translate",image_translate,
+		"function(int|float,int|float:object)",0);
+   add_function("translate_expand",image_translate_expand,
+		"function(int|float,int|float:object)",0);
+
+   add_function("paste",image_paste,
+		"function(object,int|void,int|void:object)",0);
+   add_function("paste_alpha",image_paste_alpha,
+		"function(object,int,int|void,int|void:object)",0);
+   add_function("paste_mask",image_paste_mask,
+		"function(object,object,int|void,int|void:object)",0);
+   add_function("paste_alpha_color",image_paste_alpha_color,
+		"function(object,void|int,void|int,void|int,int|void,int|void:object)",0);
+
+   add_function("setcolor",image_setcolor,
+		"function(int,int,int:object)",0);
+   add_function("setpixel",image_setpixel,
+		"function(int,int,"RGB_TYPE":object)",0);
+   add_function("getpixel",image_getpixel,
+		"function(int,int:array(int))",0);
+   add_function("line",image_line,
+		"function(int,int,int,int,"RGB_TYPE":object)",0);
+   add_function("circle",image_circle,
+		"function(int,int,int,int,"RGB_TYPE":object)",0);
+   add_function("box",image_box,
+		"function(int,int,int,int,"RGB_TYPE":object)",0);
+   add_function("tuned_box",image_tuned_box,
+		"function(int,int,int,int,array:object)",0);
+
+   add_function("gray",image_gray,
+		"function("RGB_TYPE":object)",0);
+   add_function("color",image_color,
+		"function("RGB_TYPE":object)",0);
+   add_function("change_color",image_change_color,
+		"function(int,int,int,"RGB_TYPE":object)",0);
+   add_function("invert",image_invert,
+		"function("RGB_TYPE":object)",0);
+   add_function("threshold",image_threshold,
+		"function("RGB_TYPE":object)",0);
+   add_function("distancesq",image_distancesq,
+		"function("RGB_TYPE":object)",0);
+   add_function("select_from",image_select_from,
+		"function(int,int:object)",0);
+
+   add_function("apply_matrix",image_apply_matrix,
+                "function(array(array(int|array(int))), void|int ...:object)",0);
+   add_function("modify_by_intensity",image_modify_by_intensity,
+                "function(int,int,int,int,int:object)",0);
+
+   add_function("rotate_ccw",image_ccw,
+		"function(:object)",0);
+   add_function("rotate_cw",image_cw,
+		"function(:object)",0);
+   add_function("mirrorx",image_mirrorx,
+		"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);
+   add_function("ysize",image_ysize,
+		"function(:int)",0);
+
+   add_function("map_closest",image_map_closest,
+                "function(:object)",0);
+   add_function("map_fast",image_map_fast,
+                "function(:object)",0);
+   add_function("map_fs",image_map_fs,
+                "function(:object)",0);
+   add_function("select_colors",image_select_colors,
+                "function(int:array(array(int)))",0);
+
+   add_function("noise",image_noise,
+                "function(array(float|int|array(int)),float|void,float|void,float|void,float|void:object)",0);
+   add_function("turbulence",image_turbulence,
+                "function(array(float|int|array(int)),int|void,float|void,float|void,float|void,float|void:object)",0);
+
+   add_function("dct",image_dct,
+		"function(:object)",0);
+
+   add_function("`-",image_operator_minus,
+		"function(object|array(int):object)",0);
+   add_function("`+",image_operator_plus,
+		"function(object|array(int):object)",0);
+   add_function("`*",image_operator_multiply,
+		"function(object|array(int):object)",0);
+   add_function("`&",image_operator_minimum,
+		"function(object|array(int):object)",0);
+   add_function("`|",image_operator_maximum,
+		"function(object|array(int):object)",0);
+		
+   set_init_callback(init_image_struct);
+   set_exit_callback(exit_image_struct);
+  
+   image_program=end_program();
+   add_program_constant("image",image_program, 0);
+  
+   for (i=0; i<CIRCLE_STEPS; i++) 
+      circle_sin_table[i]=(INT32)4096*sin(((double)i)*2.0*3.141592653589793/(double)CIRCLE_STEPS);
+
+   init_font_programs();
+}
+
+void pike_module_exit(void) 
+{
+  if(image_program)
+  {
+    free_program(image_program);
+    image_program=0;
+  }
+  exit_font();
+}
+
+
diff --git a/src/modules/Image/image.h b/src/modules/Image/image.h
new file mode 100644
index 0000000000000000000000000000000000000000..e5828e3ef534907004af2d29ab149c9cf3d13f2d
--- /dev/null
+++ b/src/modules/Image/image.h
@@ -0,0 +1,148 @@
+/* $Id: image.h,v 1.1 1997/02/11 08:35:43 hubbe Exp $ */
+
+#define MAX_NUMCOL 32768
+
+#define QUANT_SELECT_CACHE 6
+
+#define COLOURTYPE unsigned char
+
+#define FS_SCALE 1024
+
+typedef struct 
+{
+   COLOURTYPE r,g,b;
+} rgb_group;
+
+typedef struct 
+{
+   unsigned char r,g,b,alpha;
+} rgba_group;
+
+typedef struct
+{
+   INT32 r,g,b;
+} rgbl_group;
+
+typedef struct
+{
+   float r,g,b;
+} rgbd_group; /* use float, it gets so big otherwise... */
+
+struct image
+{
+   rgb_group *img;
+   INT32 xsize,ysize;
+   rgb_group rgb;
+   unsigned char alpha;
+};
+
+struct colortable
+{
+   int numcol;
+   struct rgb_cache
+   {
+      rgb_group index;
+      int value;
+   } cache[QUANT_SELECT_CACHE];
+   unsigned long *rgb_node;
+/*   bit
+     31..30          29..22   21..0
+     0=color         split    value
+     1=split red     on	this  
+     2=split green   =x       <=x     >x
+     3=split blue             value   value+1
+     It will fail for more than 2097152 colors. Sorry... *grin*
+ */
+   rgb_group clut[1];
+};  /* rgb_node follows, 2*numcol */
+
+
+/* colortable declarations - from quant */
+
+struct colortable *colortable_quant(struct image *img,int numcol);
+int colortable_rgb(struct colortable *ct,rgb_group rgb);
+int colortable_rgb_nearest(struct colortable *ct,rgb_group rgb);
+void colortable_free(struct colortable *ct);
+struct colortable *colortable_from_array(struct array *arr,char *from);
+
+/* encoding of a gif - from togif */
+
+struct pike_string *
+   image_encode_gif(struct image *img,struct colortable *ct,
+		    rgb_group *transparent,
+		    int floyd_steinberg);
+void image_floyd_steinberg(rgb_group *rgb,int xsize,
+			   rgbl_group *errl,
+			   int way,int *res,
+			   struct colortable *ct);
+
+int image_decode_gif(struct image *dest,struct image *dest_alpha,
+		     unsigned char *src,unsigned long len);
+
+void image_togif(INT32 args);
+void image_togif_fs(INT32 args);
+void image_fromgif(INT32 args);
+void image_gif_begin(INT32 args);
+void image_gif_add(INT32 args);
+void image_gif_add_fs(INT32 args);
+void image_gif_add_nomap(INT32 args);
+void image_gif_add_fs_nomap(INT32 args);
+void image_gif_end(INT32 args);
+void image_gif_netscape_loop(INT32 args);
+
+/* 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_translate(INT32 args);
+void image_translate_expand(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);
+ 
+/* pattern.c */
+
+void image_noise(INT32 args);
+void image_turbulence(INT32 args);
+void image_noise_init(void);
+
+/* dct.c */
+
+void image_dct(INT32 args);
+
+/* operator.c */
+
+void image_operator_minus(INT32 args);
+void image_operator_plus(INT32 args);
+void image_operator_multiply(INT32 args);
+void image_operator_maximum(INT32 args);
+void image_operator_minimum(INT32 args);
diff --git a/src/modules/Image/lzw.c b/src/modules/Image/lzw.c
new file mode 100644
index 0000000000000000000000000000000000000000..31e01592ef5734c352292c516fff1aff693a13bf
--- /dev/null
+++ b/src/modules/Image/lzw.c
@@ -0,0 +1,368 @@
+/* $Id: lzw.c,v 1.1 1997/02/11 08:35:44 hubbe Exp $ */
+
+/*
+
+lzw, used by togif
+
+Pontus Hagland, law@infovav.se
+
+this file can be used generally for lzw algoritms,
+the existanse of #define GIF_LZW is for that purpose. :-)
+/Pontus
+
+*/
+
+#include "global.h"
+
+#include "lzw.h"
+
+#define DEFAULT_OUTBYTES 16384
+#ifndef GIF_LZW
+#define STDLZWCODES 8192
+#endif
+
+static INLINE void lzw_output(struct lzw *lzw,lzwcode_t codeno)
+{
+   int bits,bitp;
+   unsigned char c;
+
+/*
+   fprintf(stderr,"%03x bits=%d codes %d %c\n",
+           codeno,lzw->codebits,lzw->codes+1,
+	   (codeno==lzw->codes+1) ? '=' : ' ');
+	   */
+#if 0
+   fprintf(stderr,"\nwrote %4d<'",codeno);
+   lzw_recurse_find_code(lzw,codeno);
+   fprintf(stderr,"' ");
+#endif
+
+   if (lzw->outpos+4>=lzw->outlen)
+      lzw->out=realloc(lzw->out,lzw->outlen*=2);
+
+   bitp=lzw->outbit;
+   c=lzw->lastout;
+   bits=lzw->codebits;
+#ifdef GIF_LZW
+   if (bits>12) bits=12;
+#endif
+
+   while (bits)
+   {
+      c|=(codeno<<bitp);
+      if (bits+bitp>=8)
+      {
+	 bits-=8-bitp;
+	 codeno>>=8-bitp;
+	 bitp=0;
+	 lzw->out[lzw->outpos++]=c;
+	 c=0;
+      }
+      else
+      {
+	 lzw->outbit=bitp+bits;
+	 lzw->lastout=c;
+	 return;
+      }
+   }
+   lzw->lastout=0;
+   lzw->outbit=0;
+}
+
+
+void lzw_init(struct lzw *lzw,int bits)
+{
+   unsigned long i;
+#ifdef GIF_LZW
+   lzw->codes=(1L<<bits)+2;
+#else
+   lzw->codes=(1L<<bits);
+#endif
+   lzw->bits=bits;
+   lzw->codebits=bits+1;
+   lzw->code=(struct lzwc*) malloc(sizeof(struct lzwc)*
+#ifdef GIF_LZW
+				   4096
+#else
+                                   (lzw->alloced=STDLZWCODES)
+#endif
+				   );
+
+   for (i=0; i<lzw->codes; i++)
+   {
+      lzw->code[i].c=(unsigned char)i;
+      lzw->code[i].firstchild=LZWCNULL;
+      lzw->code[i].next=LZWCNULL;
+   }
+   lzw->out=malloc(DEFAULT_OUTBYTES);
+   lzw->outlen=DEFAULT_OUTBYTES;
+   lzw->outpos=0;
+   lzw->current=LZWCNULL;
+   lzw->outbit=0;
+   lzw->lastout=0;
+#ifdef GIF_LZW
+   lzw_output(lzw,1L<<bits);
+#endif
+}
+
+void lzw_quit(struct lzw *lzw)
+{
+   int i;
+
+   free(lzw->code);
+   free(lzw->out);
+}
+
+#if 0
+static void lzw_recurse_find_code(struct lzw *lzw,lzwcode_t codeno)
+{
+   lzwcode_t i;
+   if (codeno<(1L<<lzw->bits))
+      fprintf(stderr,"%c",codeno);
+   else if (codeno==(1L<<lzw->bits))
+      fprintf(stderr,"(clear)");
+   else if (codeno==(1L<<lzw->bits)+1)
+      fprintf(stderr,"(end)");
+   else for (;;)
+   {
+      /* direct child? */
+      for (i=0; i<lzw->codes; i++)
+	 if (lzw->code[i].firstchild==codeno)
+	 {
+	    lzw_recurse_find_code(lzw,i);
+	    fprintf(stderr,"%c",lzw->code[codeno].c);
+	    return;
+	 }
+      for (i=0; i<lzw->codes; i++)
+	 if (lzw->code[i].next==codeno) codeno=i;
+   }
+}
+#endif
+
+void lzw_write_last(struct lzw *lzw)
+{
+   if (lzw->current)
+      lzw_output(lzw,lzw->current);
+#ifdef GIF_LZW
+   lzw_output( lzw, (1L<<lzw->bits)+1 ); /* GIF end code */
+#endif
+   if (lzw->outbit)
+      lzw->out[lzw->outpos++]=lzw->lastout;
+}
+
+void lzw_add(struct lzw *lzw,int c)
+{
+   lzwcode_t lno,lno2;
+   struct lzwc *l;
+
+   if (lzw->current==LZWCNULL) /* no current, load */
+   {
+      lzw->current=c;
+      return;
+   }
+
+   lno=lzw->code[lzw->current].firstchild; /* check if we have this sequence */
+   while (lno!=LZWCNULL)
+   {
+      if (lzw->code[lno].c==c && lno!=lzw->codes-1 )
+      {
+	 lzw->current=lno;
+	 return;
+      }
+      lno=lzw->code[lno].next;
+   }
+
+
+#ifdef GIF_LZW
+   if (lzw->codes==4096)  /* needs more than 12 bits */
+   {
+      int i;
+
+      lzw_output(lzw,lzw->current);
+
+      for (i=0; i<(1L<<lzw->bits); i++)
+	 lzw->code[i].firstchild=LZWCNULL;
+      lzw->codes=(1L<<lzw->bits)+2;
+      
+      /* output clearcode, 1000... (bits) */
+      lzw_output(lzw,1L<<lzw->bits);
+
+      lzw->codebits=lzw->bits+1;
+      lzw->current=c;
+      return;
+   }
+#else
+   if (lzw->codes==MAXLZWCODES)
+      realloc lzwc->code... move lzw->current
+#endif
+
+   /* output current code no, make new & reset */
+
+   lzw_output(lzw,lzw->current);
+
+   lno=lzw->code[lzw->current].firstchild;
+   lno2=lzw->codes;
+   l=lzw->code+lno2;
+   l->next=lno;
+   l->firstchild=LZWCNULL;
+   l->c=c;
+   lzw->code[lzw->current].firstchild=lno2;
+
+   lzw->codes++;
+   if (lzw->codes>(unsigned long)(1L<<lzw->codebits)) lzw->codebits++;
+
+   lzw->current=c;
+}
+
+#undef UNPACK_DEBUG
+
+#ifdef GIF_LZW
+unsigned long lzw_unpack(unsigned char *dest,unsigned long destlen,
+			 unsigned char *src,unsigned long srclen,
+			 int bits)
+{
+   struct lzwuc
+   {
+      unsigned char c;
+      lzwcode_t parent;
+      lzwcode_t len; /* no string is longer then that ... */
+   } *code;
+
+   static unsigned short mask[16]={0,1,3,7,017,037,077,0177,0377,0777,01777,
+				   03777,07777,01777,03777,07777};
+
+   unsigned long wrote=0;
+   int i,cbits,cbit,clear=(1<<bits),end=(1<<bits)+1;
+   lzwcode_t current,last,used,nextlast;
+   unsigned long store;
+   unsigned char *srcend=src+srclen,*destend=dest+destlen;
+   unsigned char first=0;
+
+   code=malloc(sizeof(struct lzwuc)*4096);
+   if (!code) return 0;
+
+   for (i=0; i<(1<<bits); i++)
+   {
+      code[i].c=i;
+      code[i].parent=LZWCNULL;
+      code[i].len=1;
+   }
+
+   cbit=0;
+   cbits=bits+1;
+   store=0;
+   last=LZWCNULL;
+   used=end+1;
+
+   while (src!=srcend)
+   {
+      if (cbit>cbits)
+      {
+	 current=store>>(32-cbits);
+	 cbit-=cbits;
+      }
+      else 
+      {
+	 while (cbits-cbit>=8)
+	 {
+	    store=(store>>8)|((*(src++))<<24);
+	    cbit+=8;
+	    if (src==srcend) { free(code); return wrote; }
+	 }
+	 store=(store>>8)|((*(src++))<<24);
+	 cbit+=8;
+	 current=(store>>(32-(cbit)))&mask[cbits];
+	 cbit-=cbits;
+      }
+
+#ifdef UNPACK_DEBUG
+      fprintf(stderr,"%03x ",current);
+#endif
+      
+      if (current==clear) /* clear tree */
+      {
+	 for (i=0; i<end-2; i++)
+	    code[i].parent=LZWCNULL;
+	 last=LZWCNULL;
+	 used=end+1;
+	 if (cbits!=bits+1)
+	 {
+	    cbits=bits+1;
+	    cbit++;
+	 }
+      }
+      else if (current==end) /* end of data */
+	 break;
+      else if (last==LZWCNULL)
+      {
+	 last=current;
+	 if (last>end) break;
+	 *(dest++)=(unsigned char)current;
+	 wrote++;
+	 first=current;
+      }
+      else
+      {
+	 lzwcode_t n;
+	 unsigned char *dest2;
+
+	 if (code[current].len+dest>destend) /* no space, cancel */
+	    break; 
+
+	 nextlast=current;
+
+	 if (current>=used)
+	 {
+	    *(dest++)=(unsigned char)first;
+	    wrote++;
+	    current=last;
+	 }
+	 
+	 dest+=code[current].len;
+	 wrote+=code[current].len;
+
+	 dest2=dest;
+	 n=current;
+	 *--dest2=code[n].c;
+	 while (n>end)
+	 {
+	    n=code[n].parent;
+	    *--dest2=code[n].c;
+	 }
+
+
+/*
+	 *dest=0;
+	 fprintf(stderr,"read %4d>'",current);
+	 for (i=0; i<code[current].len; i++)
+	    fprintf(stderr,"%c",(dest-code[current].len)[i]);
+	 fprintf(stderr,"' == ");
+	 for (i=0; i<code[current].len; i++)
+	    fprintf(stderr,"%02x",(dest-code[current].len)[i]);
+	 fprintf(stderr,"\n");
+*/
+
+	 if (used<4096)
+	 {
+	    code[used].c=code[n].c;
+	    code[used].parent=last;
+	    code[used].len=code[last].len+1;
+	    used++;
+
+	    if (used>=(1<<cbits)) 
+	    {
+	       cbits++;
+#ifdef UNPACK_DEBUG
+	       fprintf(stderr,"[%d bits]",cbits);
+#endif
+	    }
+	 }
+
+	 last=nextlast;
+      }
+   }
+   free(code);
+   return wrote;
+}
+#endif
+
diff --git a/src/modules/Image/lzw.h b/src/modules/Image/lzw.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b8f3030309c168770e6063aa061f00f646cfc99
--- /dev/null
+++ b/src/modules/Image/lzw.h
@@ -0,0 +1,40 @@
+/* $Id: lzw.h,v 1.1 1997/02/11 08:35:44 hubbe Exp $ */
+
+#define GIF_LZW
+
+#ifdef GIF_LZW
+typedef unsigned short lzwcode_t; /* no more than 12 bits used */
+#else
+typedef unsigned long lzwcode_t;
+#endif
+
+struct lzw
+{
+   unsigned long codes;
+   unsigned long bits; /* initial encoding bits */
+   unsigned long codebits; /* current encoding bits */
+   unsigned long outlen,outpos,outbit;
+   unsigned char *out,lastout;
+   struct lzwc 
+   {
+      unsigned char c;
+      lzwcode_t firstchild;
+      lzwcode_t next;
+   } *code;
+   lzwcode_t current,firstfree;
+#ifndef GIF_LZW
+   unsigned long alloced;
+#endif
+};
+
+#define LZWCNULL ((lzwcode_t)(~0))
+
+void lzw_add(struct lzw *lzw,int c);
+void lzw_quit(struct lzw *lzw);
+void lzw_init(struct lzw *lzw,int bits);
+unsigned long lzw_unpack(unsigned char *dest,unsigned long destlen,
+			 unsigned char *src,unsigned long srclen,
+			 int bits);
+
+
+
diff --git a/src/modules/Image/matrix.c b/src/modules/Image/matrix.c
new file mode 100644
index 0000000000000000000000000000000000000000..4b207261b4d50b854f9c7e178b57b89f5528eec5
--- /dev/null
+++ b/src/modules/Image/matrix.c
@@ -0,0 +1,977 @@
+/* $Id: matrix.c,v 1.1 1997/02/11 08:35:44 hubbe Exp $ */
+
+#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 "threads.h"
+#include "error.h"
+
+#include "image.h"
+
+extern 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))
+
+#if 0
+#include <sys/resource.h>
+#define CHRONO(X) chrono(X)
+
+static void chrono(char *x)
+{
+   struct rusage r;
+   static struct rusage rold;
+   getrusage(RUSAGE_SELF,&r);
+   fprintf(stderr,"%s: %ld.%06ld - %ld.%06ld\n",x,
+	   r.ru_utime.tv_sec,r.ru_utime.tv_usec,
+
+	   ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?-1:0)
+	   +r.ru_utime.tv_sec-rold.ru_utime.tv_sec,
+           ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?1000000:0)
+           + r.ru_utime.tv_usec-rold.ru_utime.tv_usec
+	   );
+
+   rold=r;
+}
+#else
+#define CHRONO(X)
+#endif
+
+/***************** 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+=(src)->r*(factor), \
+    (dest)->g+=(src)->g*(factor), \
+    (dest)->b+=(src)->b*(factor)) 
+#define scale_add_pixel(dest,dx,src,sx,factor) \
+   _scale_add_rgb(dest,src,factor)
+
+static INLINE void scale_add_line(rgbd_group *new,INT32 yn,INT32 newx,
+				  rgb_group *img,INT32 y,INT32 xsize,
+				  double py,double dx)
+{
+   INT32 x,xd;
+   double xn,xndxd;
+   new=new+yn*newx;
+   img=img+y*xsize;
+   for (x=0,xn=0; x<xsize; img++,x++,xn+=dx)
+   {
+      if ((INT32)xn<(INT32)(xn+dx))
+      {
+	 xndxd=py*(1.0-decimals(xn));
+	 if (xndxd)
+	    scale_add_pixel(new,(INT32)xn,img,x,xndxd);
+	 if (dx>=1.0 && (xd=(INT32)(xn+dx)-(INT32)(xn))>1) 
+            while (--xd)
+	    {
+	       new++;
+               scale_add_pixel(new,(INT32)(xn+xd),img,x,py);
+	    }
+	 xndxd=py*decimals(xn+dx);
+	 new++;
+	 if (xndxd)
+	    scale_add_pixel(new,(INT32)(xn+dx),img,x,xndxd);
+      }
+      else
+         scale_add_pixel(new,(int)xn,img,x,py*dx);
+   }
+}
+
+void img_scale(struct image *dest,
+	       struct image *source,
+	       INT32 newx,INT32 newy)
+{
+   rgbd_group *new,*s;
+   rgb_group *d;
+   INT32 y,yd;
+   double yn,dx,dy;
+
+CHRONO("scale begin");
+
+   if (dest->img) { free(dest->img); dest->img=NULL; }
+
+   if (!THIS->img || newx<=0 || newy<=0) return; /* no way */
+
+   THREADS_ALLOW();
+   new=malloc(newx*newy*sizeof(rgbd_group) +1);
+   if (!new) error("Out of memory!\n");
+
+   for (y=0; y<newx*newy; y++) 
+      new[y].r=new[y].g=new[y].b=0.0;
+   
+   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))
+      {
+	 if (1.0-decimals(yn))
+	    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);
+	 if (decimals(yn+dy))
+	    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=d=malloc(newx*newy*sizeof(rgb_group) +1);
+   if (!d) { free(new); error("Out of memory!\n"); }
+
+CHRONO("transfer begin");
+
+   s=new;
+   y=newx*newy;
+   while (y--)
+   {
+      d->r=min((int)(s->r+0.5),255);
+      d->g=min((int)(s->g+0.5),255);
+      d->b=min((int)(s->b+0.5),255);
+      d++; s++;
+   }
+
+   dest->xsize=newx;
+   dest->ysize=newy;
+
+   free(new);
+
+CHRONO("scale end");
+   THREADS_DISALLOW();
+}
+
+/* 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 */
+
+   THREADS_ALLOW();
+   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);
+     }
+   THREADS_DISALLOW();
+}
+
+
+
+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,xs,ys;
+   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=xs=THIS->xsize;
+   ys=THIS->ysize;
+   src=THIS->img+THIS->xsize-1;
+   dest=img->img;
+
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=ys;
+      while (j--) *(dest++)=*(src),src+=xs;
+      src--;
+      src-=xs*ys;
+   }
+   THREADS_DISALLOW();
+
+   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;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=is->ysize;
+      while (j--) *(dest++)=*(src),src+=is->xsize;
+      src--;
+      src-=is->xsize*is->ysize;
+   }
+   THREADS_DISALLOW();
+}
+
+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;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=is->ysize;
+      while (j--) *(--dest)=*(src),src+=is->xsize;
+      src--;
+      src-=is->xsize*is->ysize;
+   }
+   THREADS_DISALLOW();
+}
+
+void image_cw(INT32 args)
+{
+   INT32 i,j,xs,ys;
+   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");
+   }
+   ys=img->xsize=THIS->ysize;
+   i=xs=img->ysize=THIS->xsize;
+
+   src=THIS->img+THIS->xsize-1;
+   dest=img->img+THIS->xsize*THIS->ysize;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=ys;
+      while (j--) *(--dest)=*(src),src+=xs;
+      src--;
+      src-=xs*ys;
+   }
+   THREADS_DISALLOW();
+
+   push_object(o);
+}
+
+void image_mirrorx(INT32 args)
+{
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+   INT32 i,j,xs;
+
+   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;
+   xs=THIS->xsize;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=xs;
+      while (j--) *(dest++)=*(src--);
+      src+=xs*2;
+   }
+   THREADS_DISALLOW();
+
+   push_object(o);
+}
+
+void image_mirrory(INT32 args)
+{
+   rgb_group *src,*dest;
+   struct object *o;
+   struct image *img;
+   INT32 i,j,xs;
+
+   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;
+   xs=THIS->xsize;
+   THREADS_ALLOW();
+   while (i--)
+   {
+      j=xs;
+      while (j--) *(dest++)=*(src++);
+      src-=xs*2;
+   }
+   THREADS_DISALLOW();
+
+   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;
+
+   THREADS_ALLOW();
+   xmod=diff/src->ysize;
+   rgb=dest->rgb;
+
+   CHRONO("skewx begin\n");
+
+   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;
+   }
+   THREADS_DISALLOW();
+
+   CHRONO("skewx end\n");
+}
+
+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;
+   
+   THREADS_ALLOW();
+   ymod=diff/src->xsize;
+   rgb=dest->rgb;
+
+CHRONO("skewy begin\n");
+
+   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;
+   }
+   THREADS_DISALLOW();
+
+CHRONO("skewy end\n");
+
+}
+
+void image_skewx(INT32 args)
+{
+   float diff=0;
+   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=0;
+   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=0;
+   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=0;
+   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=0;
+   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-225)/360);
+   else if (angle>225) angle-=360*(int)((angle+135)/360);
+   if (angle<-45) 
+   { 
+      img_ccw(THIS,&dest2); 
+      angle+=90; 
+   }
+   else if (angle>135) 
+   {
+      img_ccw(THIS,&d0); 
+      img_ccw(&d0,&dest2);  
+      angle-=180;
+   }
+   else if (angle>45) 
+   { 
+      img_cw(THIS,&dest2);  
+      angle-=90; 
+   }
+   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);
+}
+
+void img_translate(INT32 args,int expand)
+{
+   float xt,yt;
+   int y,x;
+   struct object *o;
+   struct image *img;
+   rgb_group *s,*d;
+
+   if (args<2) error("illegal number of arguments to image->translate()\n");
+   
+   if (sp[-args].type==T_FLOAT) xt=sp[-args].u.float_number;
+   else if (sp[-args].type==T_INT) xt=sp[-args].u.integer;
+   else error("illegal argument 1 to image->translate()\n");
+
+   if (sp[1-args].type==T_FLOAT) yt=sp[1-args].u.float_number;
+   else if (sp[1-args].type==T_INT) yt=sp[1-args].u.integer;
+   else error("illegal argument 2 to image->translate()\n");
+
+   getrgb(THIS,2,args,"image->translate()\n");
+
+   xt-=floor(xt);
+   yt-=floor(yt);
+   
+   o=clone(image_program,0);
+   img=(struct image*)o->storage;
+
+   img->xsize=THIS->xsize+(xt!=0);
+   img->ysize=THIS->ysize+(xt!=0);
+
+   if (!(img->img=malloc(sizeof(rgb_group)*img->xsize*img->ysize+1)))
+   {
+      free_object(o);
+      error("Out of memory\n");
+   }
+
+   if (!xt)
+   {
+      memcpy(img->img,THIS->img,sizeof(rgb_group)*THIS->xsize*THIS->ysize);
+   }
+   else
+   {
+      float xn=1-xt;
+
+      d=img->img;
+      s=THIS->img;
+
+      for (y=0; y<img->ysize; y++)
+      {
+	 x=THIS->xsize-1;
+	 if (!expand)
+	    d->r=ROUND(THIS->rgb.r*xt+s->r*xn),
+	    d->g=ROUND(THIS->rgb.g*xt+s->g*xn),
+	    d->b=ROUND(THIS->rgb.b*xt+s->b*xn);
+	 else
+	    d->r=s->r, d->g=s->g, d->b=s->b;
+	 d++; s++;
+	 while (x--)
+	 {
+	    d->r=ROUND(s->r*xn+s[1].r*xt),
+	    d->g=ROUND(s->g*xn+s[1].g*xt),
+	    d->b=ROUND(s->b*xn+s[1].b*xt);
+	    d++; s++;
+	 }
+	 if (!expand)
+	    d->r=ROUND(s->r*xn+THIS->rgb.r*xt),
+	    d->g=ROUND(s->g*xn+THIS->rgb.g*xt),
+	    d->b=ROUND(s->b*xn+THIS->rgb.b*xt);
+	 else
+	    d->r=s->r, d->g=s->g, d->b=s->b;
+	 d++;
+      }
+   }
+
+   if (yt)
+   {
+      float yn=1-yt;
+      int xsz=img->xsize;
+
+      d=s=img->img;
+
+      for (x=0; x<img->xsize; x++)
+      {
+	 y=THIS->ysize-1;
+	 if (!expand)
+	    d->r=ROUND(THIS->rgb.r*yt+s->r*yn),
+	    d->g=ROUND(THIS->rgb.g*yt+s->g*yn),
+	    d->b=ROUND(THIS->rgb.b*yt+s->b*yn);
+	 else
+	    d->r=s->r, d->g=s->g, d->b=s->b;
+	 d+=xsz; s+=xsz;
+	 while (y--)
+	 {
+	    d->r=ROUND(s->r*yn+s[xsz].r*yt),
+	    d->g=ROUND(s->g*yn+s[xsz].g*yt),
+	    d->b=ROUND(s->b*yn+s[xsz].b*yt);
+	    d+=xsz; s+=xsz;
+	 }
+	 if (!expand)
+	    d->r=ROUND(s->r*yn+THIS->rgb.r*yt),
+	    d->g=ROUND(s->g*yn+THIS->rgb.g*yt),
+	    d->b=ROUND(s->b*yn+THIS->rgb.b*yt);
+	 else
+	    d->r=s->r, d->g=s->g, d->b=s->b;
+	 d-=xsz*(img->ysize-1)-1;
+	 s-=xsz*THIS->ysize-1;
+      }
+   }
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+
+void image_translate_expand(INT32 args)
+{
+   img_translate(args,1);
+}
+
+void image_translate(INT32 args)
+{
+   img_translate(args,0);
+}
diff --git a/src/modules/Image/operator.c b/src/modules/Image/operator.c
new file mode 100644
index 0000000000000000000000000000000000000000..d411eb6474c15c75c55b2f5ccebd38d0100e1abf
--- /dev/null
+++ b/src/modules/Image/operator.c
@@ -0,0 +1,186 @@
+/* $Id: operator.c,v 1.1 1997/02/11 08:35:45 hubbe Exp $ */
+#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 "threads.h"
+
+#include "image.h"
+
+extern struct program *image_program;
+#define THIS ((struct image *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+#define absdiff(a,b) ((a)<(b)?((b)-(a)):((a)-(b)))
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)<(b)?(b):(a))
+
+#define STANDARD_OPERATOR_HEADER(what) \
+   struct object *o;			   			\
+   struct image *img,*oper;		   			\
+   rgb_group *s1,*s2,*d,rgb;		   			\
+   INT32 i;				   			\
+					   			\
+   if (!THIS->img) error("no image\n");	   			\
+   		   			   			\
+   if (args && sp[-args].type==T_ARRAY				\
+       && sp[-args].u.array->size>=3				\
+       && sp[-args].u.array->item[0].type==T_INT		\
+       && sp[-args].u.array->item[1].type==T_INT		\
+       && sp[-args].u.array->item[2].type==T_INT)		\
+   {								\
+      rgb.r=sp[-args].u.array->item[0].u.integer;		\
+      rgb.g=sp[-args].u.array->item[1].u.integer;		\
+      rgb.b=sp[-args].u.array->item[2].u.integer;		\
+      oper=NULL;						\
+   }								\
+   else								\
+   {								\
+      if (args<1 || sp[-args].type!=T_OBJECT			\
+       || !sp[-args].u.object					\
+       || sp[-args].u.object->prog!=image_program)		\
+      error("illegal arguments to image->"what"()\n");		\
+   		   		   				\
+      oper=(struct image*)sp[-args].u.object->storage;		\
+      if (!oper->img) error("no image (operand)\n");		\
+      if (oper->xsize!=THIS->xsize 		   		\
+          || oper->ysize!=THIS->ysize) 		   		\
+         error("operands differ in size (image->"what")");	\
+   }  	   		   		   		        \
+		   		   		   		\
+   push_int(THIS->xsize);		   			\
+   push_int(THIS->ysize);		   			\
+   o=clone(image_program,2);		   			\
+   img=(struct image*)o->storage;		   		\
+   if (!img->img) { free_object(o); error("out of memory\n"); }	\
+		   		   		   		\
+   pop_n_elems(args);		   		   		\
+   push_object(o);		   		   		\
+   		   		   		   		\
+   s1=THIS->img;		   		   		\
+   if (oper) s2=oper->img; else s2=NULL;   		   	\
+   d=img->img;		   		   			\
+		   		   		   		\
+   i=img->xsize*img->ysize;			   		\
+   THREADS_ALLOW();                                             \
+   if (oper)
+
+
+void image_operator_minus(INT32 args)
+{
+STANDARD_OPERATOR_HEADER("'-")
+   while (i--)
+   {
+      d->r=absdiff(s1->r,s2->r);
+      d->g=absdiff(s1->g,s2->g);
+      d->b=absdiff(s1->b,s2->b);
+      s1++; s2++; d++;
+   }
+   else
+   while (i--)
+   {
+      d->r=absdiff(s1->r,rgb.r);
+      d->g=absdiff(s1->g,rgb.g);
+      d->b=absdiff(s1->b,rgb.b);
+      s1++; d++;
+   }
+   THREADS_DISALLOW();
+}
+
+void image_operator_plus(INT32 args)
+{
+STANDARD_OPERATOR_HEADER("'+")
+   while (i--)
+   {
+      d->r=max(s1->r+s2->r,255);
+      d->g=max(s1->g+s2->g,255);
+      d->b=max(s1->b+s2->b,255);
+      s1++; s2++; d++; 
+   }
+   else
+   while (i--)
+   {
+      d->r=max(s1->r+rgb.r,255);
+      d->g=max(s1->g+rgb.g,255);
+      d->b=max(s1->b+rgb.b,255);
+      s1++; d++;
+   }
+   THREADS_DISALLOW();
+}
+
+void image_operator_multiply(INT32 args)
+{
+   double q=1/255.0;
+STANDARD_OPERATOR_HEADER("'+")
+   while (i--)
+   {
+      d->r=floor(s1->r*s2->r*q+0.5);
+      d->g=floor(s1->g*s2->g*q+0.5);
+      d->b=floor(s1->b*s2->b*q+0.5);
+      s1++; s2++; d++; 
+   }
+   else
+   while (i--)
+   {
+      d->r=floor(s1->r*rgb.r*q+0.5);
+      d->g=floor(s1->g*rgb.g*q+0.5);
+      d->b=floor(s1->b*rgb.b*q+0.5);
+      s1++; d++; 
+   }
+   THREADS_DISALLOW();
+}
+
+void image_operator_maximum(INT32 args)
+{
+STANDARD_OPERATOR_HEADER("'| 'maximum'")
+   while (i--)
+   {
+      d->r=max(s1->r,s2->r);
+      d->g=max(s1->g,s2->g);
+      d->b=max(s1->b,s2->b);
+      s1++; s2++; d++; 
+   }
+   else
+   while (i--)
+   {
+      d->r=max(s1->r,rgb.r);
+      d->g=max(s1->g,rgb.g);
+      d->b=max(s1->b,rgb.b);
+      s1++; s2++; d++; 
+   }
+   THREADS_DISALLOW();
+}
+
+void image_operator_minimum(INT32 args)
+{
+STANDARD_OPERATOR_HEADER("'& 'minimum'")
+   while (i--)
+   {
+      d->r=min(s1->r,s2->r);
+      d->g=min(s1->g,s2->g);
+      d->b=min(s1->b,s2->b);
+      s1++; s2++; d++; 
+   }
+   else
+   while (i--)
+   {
+      d->r=min(s1->r,rgb.r);
+      d->g=min(s1->g,rgb.g);
+      d->b=min(s1->b,rgb.b);
+      s1++; d++; 
+   }
+   THREADS_DISALLOW();
+}
+
+
diff --git a/src/modules/Image/pattern.c b/src/modules/Image/pattern.c
new file mode 100644
index 0000000000000000000000000000000000000000..f96e2dd5fe5a7568ea7000b24ddb4642d61cc581
--- /dev/null
+++ b/src/modules/Image/pattern.c
@@ -0,0 +1,309 @@
+/* $Id: pattern.c,v 1.1 1997/02/11 08:35:45 hubbe Exp $ */
+
+#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"
+
+extern 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))
+#define testrange(x) max(min((x),255),0)
+
+/**************** noise ************************/
+
+#define NOISE_PTS 512
+#define NOISE_PX 173
+#define NOISE_PY 263
+#define NOISE_PZ 337
+#define NOISE_PHI 0.6180339
+static unsigned short noise_p1[NOISE_PTS],noise_p2[NOISE_PTS];
+
+#define COLORRANGE_LEVELS 1024
+
+#define FRAC(X) ((X)-floor(X))
+
+static INLINE double noise(double Vx,double Vy,unsigned short *noise_p)
+{
+   int Ax[3],Ay[3];
+   int n,i,j;
+   double Sx[3],Sy[3];
+   double sum,dsum,f,fx,fy;
+
+   fx=floor(Vx);
+   fy=floor(Vy);
+   
+   for (n=0; n<3; n++) 
+   {
+      Ax[n]=(int)floor(NOISE_PX*FRAC( (fx+n)*NOISE_PHI ));
+      Ay[n]=(int)floor(NOISE_PY*FRAC( (fy+n)*NOISE_PHI ));
+   }
+   
+   f=FRAC(Vx);
+   Sx[0]=0.5-f+0.5*f*f;
+   Sx[1]=0.5+f-f*f;
+   Sx[2]=0.5*f*f;
+
+   f=FRAC(Vy);
+   Sy[0]=0.5-f+0.5*f*f;
+   Sy[1]=0.5+f-f*f;
+   Sy[2]=0.5*f*f;
+
+   sum=0;
+   for (i=0; i<3; i++)
+   {
+      for (j=0,dsum=0; j<3; j++)
+	 dsum+=Sy[j]*noise_p[ (Ax[i]+Ay[j]) & (NOISE_PTS-1) ];
+      sum+=Sx[i]*dsum;
+   }
+   return sum;
+}
+
+static INLINE double turbulence(double x,double y,int octaves)
+{
+   double t=0;
+   double mul=1;
+   while (octaves-->0)
+   {
+      t+=noise(x*mul,y*mul,noise_p1)*mul;
+      mul*=0.5;
+   }
+   return t;
+}
+
+static void init_colorrange(rgb_group *cr,struct svalue *s,char *where)
+{
+   float *v,*vp;
+   int i,n,k;
+   struct svalue s2,s3;
+   rgbd_group lrgb,*rgbp,*rgb;
+   float fr,fg,fb,q;
+   int b;
+
+   if (s->type!=T_ARRAY)
+      error("Illegal colorrange to %s\n",where);
+   else if (s->u.array->size<2)
+      error("Colorrange array too small (meaningless) (to %s)\n",where);
+
+   s2.type=T_INT;
+   s3.type=T_INT; /* don't free these */
+
+   vp=v=(void*)xalloc(sizeof(float)*(s->u.array->size/2+1));
+   rgbp=rgb=(void*)xalloc(sizeof(rgbd_group)*(s->u.array->size/2+1));
+
+   for (i=0; i<s->u.array->size-1; i+=2)
+   {
+      array_index(&s2,s->u.array,i);
+      if (s2.type==T_INT) *vp=s2.u.integer;
+      else if (s2.type==T_FLOAT) *vp=s2.u.float_number;
+      else *vp=0;
+      if (*vp>1) *vp=1;
+      else if (*vp<0) *vp=0;
+      vp++;
+
+      array_index(&s2,s->u.array,i+1);
+      
+      if (s2.type==T_INT)
+	 rgbp->r=rgbp->g=rgbp->b=testrange( s2.u.integer );
+      else if ( s2.type==T_ARRAY
+		&& s2.u.array->size>=3 )
+      {
+	 array_index(&s3,s2.u.array,0);
+	 if (s3.type==T_INT) rgbp->r=testrange( s3.u.integer );
+	 else rgbp->r=0;
+	 array_index(&s3,s2.u.array,1);
+	 if (s3.type==T_INT) rgbp->g=testrange( s3.u.integer );
+	 else rgbp->g=0;
+	 array_index(&s3,s2.u.array,2);
+	 if (s3.type==T_INT) rgbp->b=testrange( s3.u.integer );
+	 else rgbp->b=0;
+      }
+      else
+	 rgbp->r=rgbp->g=rgbp->b=0;
+      rgbp++;
+   }
+   *vp=v[0]+1+1.0/(COLORRANGE_LEVELS-1);
+   lrgb=*rgbp=rgb[0]; /* back to original color */
+
+   for (k=1,i=v[0]*(COLORRANGE_LEVELS-1); k<=s->u.array->size/2; k++)
+   {
+      n=v[k]*(COLORRANGE_LEVELS-1);
+
+      if (n>i)
+      {
+	 q=1/((float)(n-i));
+   
+	 fr=(rgb[k].r-lrgb.r)*q;
+	 fg=(rgb[k].g-lrgb.g)*q;
+	 fb=(rgb[k].b-lrgb.b)*q;
+
+	 for (b=0;i<n;i++,b++)
+	 {
+	    cr[i&(COLORRANGE_LEVELS-1)].r=(unsigned char)(lrgb.r+fr*b);
+	    cr[i&(COLORRANGE_LEVELS-1)].g=(unsigned char)(lrgb.g+fg*b);
+	    cr[i&(COLORRANGE_LEVELS-1)].b=(unsigned char)(lrgb.b+fb*b);
+	 }
+      }
+      lrgb=rgb[k];
+   }
+
+   free_svalue(&s3);
+   free_svalue(&s2);
+   free(v);
+   free(rgb);
+}
+
+#define GET_FLOAT_ARG(sp,args,n,def,where) \
+   ( (args>n) \
+      ? ( (sp[n-args].type==T_INT) ? (double)(sp[n-args].u.integer) \
+	  : ( (sp[n-args].type==T_FLOAT) ? sp[n-args].u.float_number \
+	      : ( error("illegal argument(s) to "where"\n"), 0.0 ) ) ) \
+      : def )
+#define GET_INT_ARG(sp,args,n,def,where) \
+   ( (args>n) \
+      ? ( (sp[n-args].type==T_INT) ? sp[n-args].u.integer \
+	  : ( (sp[n-args].type==T_FLOAT) ? (int)(sp[n-args].u.float_number) \
+	      : ( error("illegal argument(s) to "where"\n"), 0.0 ) ) ) \
+      : def )
+
+void image_noise(INT32 args)
+{
+/* parametrar: 	array(float|int|array(int)) colorrange,
+                float scale=0.1,
+		float xdiff=0,
+		float ydiff=0,
+   		float cscale=1
+*/
+   int x,y;
+   rgb_group cr[COLORRANGE_LEVELS];
+   double scale,xdiff,ydiff,cscale,xp,yp;
+   rgb_group *d;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+
+   if (args<1) error("too few arguments to image->noise()\n");
+
+   scale=GET_FLOAT_ARG(sp,args,1,0.1,"image->noise");
+   xdiff=GET_FLOAT_ARG(sp,args,2,0,"image->noise");
+   ydiff=GET_FLOAT_ARG(sp,args,3,0,"image->noise");
+   cscale=GET_FLOAT_ARG(sp,args,4,1,"image->noise");
+
+   init_colorrange(cr,sp-args,"image->noise()");
+
+   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");
+   }
+
+   cscale=(32768*cscale)/COLORRANGE_LEVELS;
+
+   d=img->img;
+   for (y=THIS->ysize,xp=xdiff; y--; xp+=1.0)
+      for (x=THIS->xsize,yp=ydiff; x--; yp+=1.0)
+      {
+	 *(d++)=
+	    cr[(int)((noise((double)x*scale,(double)y*scale,noise_p1)
+		      +noise((double)(x*0.5+y*0.8660254037844386)*scale,
+			     (double)(-y*0.5+x*0.8660254037844386)*scale,
+			     noise_p2))
+		     *cscale)&(COLORRANGE_LEVELS-1)];
+      }
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+void image_turbulence(INT32 args)
+{
+/* parametrar: 	array(float|int|array(int)) colorrange,
+		int octaves=3,
+                float scale=0.1,
+		float xdiff=0,
+		float ydiff=0,
+   		float cscale=0.001
+*/
+   int x,y,octaves;
+   rgb_group cr[COLORRANGE_LEVELS];
+   double scale,xdiff,ydiff,cscale,xp,yp;
+   rgb_group *d;
+   struct object *o;
+   struct image *img;
+
+   if (!THIS->img) error("no image\n");
+
+   if (args<1) error("too few arguments to image->turbulence()\n");
+
+   octaves=GET_INT_ARG(sp,args,1,3,"image->turbulence");
+   scale=GET_FLOAT_ARG(sp,args,2,0.1,"image->turbulence");
+   xdiff=GET_FLOAT_ARG(sp,args,3,0,"image->turbulence");
+   ydiff=GET_FLOAT_ARG(sp,args,4,0,"image->turbulence");
+   cscale=GET_FLOAT_ARG(sp,args,5,0.001,"image->turbulence");
+
+   init_colorrange(cr,sp-args,"image->turbulence()");
+
+   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");
+   }
+
+   cscale=(32768*cscale)/COLORRANGE_LEVELS;
+
+   d=img->img;
+   for (y=THIS->ysize,xp=xdiff; y--; xp+=1.0)
+      for (x=THIS->xsize,yp=ydiff; x--; yp+=1.0)
+      {
+#if 0
+	 if (y==0 && x<10)
+	 {
+	    fprintf(stderr,"%g*%g=%d => %d\n",
+		    turbulence(xp*scale,yp*scale,octaves),
+		    cscale,
+		    (INT32)(turbulence(xp*scale,yp*scale,octaves)*cscale),
+		    (INT32)(turbulence(xp*scale,yp*scale,octaves)*cscale)&(COLORRANGE_LEVELS)-1 );
+	 }
+#endif
+	 *(d++)=
+	    cr[(INT32)(turbulence(xp*scale,yp*scale,octaves)*cscale)
+	       & (COLORRANGE_LEVELS-1)];
+      }
+
+   pop_n_elems(args);
+   push_object(o);
+}
+
+
+void image_noise_init(void)
+{
+   int n;
+   for (n=0; n<NOISE_PTS; n++)
+   {
+      noise_p1[n]=(unsigned short)(rand()&32767);
+      noise_p2[n]=(unsigned short)(rand()&32767);
+   }
+}
diff --git a/src/modules/Image/pnm.c b/src/modules/Image/pnm.c
new file mode 100644
index 0000000000000000000000000000000000000000..478047a8f60737a183121bf6e5f933b192067224
--- /dev/null
+++ b/src/modules/Image/pnm.c
@@ -0,0 +1,172 @@
+/* $Id: pnm.c,v 1.1 1997/02/11 08:35:45 hubbe Exp $ */
+
+#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/quant.c b/src/modules/Image/quant.c
new file mode 100644
index 0000000000000000000000000000000000000000..60c3e08a304e1a054cda54a4372c7a7ea0cc7a44
--- /dev/null
+++ b/src/modules/Image/quant.c
@@ -0,0 +1,924 @@
+#include <config.h>
+/* $Id: quant.c,v 1.1 1997/02/11 08:35:46 hubbe Exp $ */
+
+/*
+
+quant, used by image when making gif's (mainly)
+
+Pontus Hagland, law@infovav.se
+David K�gedal, kg@infovav.se
+
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#include "types.h"
+#include "error.h"
+#include "global.h"
+#include "array.h"
+
+#include "threads.h"
+
+
+#include "image.h"
+
+/*
+#define QUANT_DEBUG_GAP
+#define QUANT_DEBUG_POINT
+#define QUANT_DEBUG
+#define QUANT_DEBUG_RGB
+*/
+
+#define QUANT_MAXIMUM_NUMBER_OF_COLORS 65535
+
+/**********************************************************************/
+
+#if 0
+#include <sys/resource.h>
+#define CHRONO(X) chrono(X)
+
+static void chrono(char *x)
+{
+   struct rusage r;
+   static struct rusage rold;
+   getrusage(RUSAGE_SELF,&r);
+   fprintf(stderr,"%s: %ld.%06ld - %ld.%06ld\n",x,
+	   r.ru_utime.tv_sec,r.ru_utime.tv_usec,
+
+	   ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?-1:0)
+	   +r.ru_utime.tv_sec-rold.ru_utime.tv_sec,
+           ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?1000000:0)
+           + r.ru_utime.tv_usec-rold.ru_utime.tv_usec
+	   );
+
+   rold=r;
+}
+#else
+#define CHRONO(X)
+#endif
+
+/**********************************************************************/
+
+#define sq(x) ((x)*(x))
+#define DISTANCE(A,B) \
+   (sq(((int)(A).r)-((int)(B).r)) \
+    +sq(((int)(A).g)-((int)(B).g)) \
+    +sq(((int)(A).b)-((int)(B).b)))
+
+
+typedef struct
+{
+  rgb_group rgb;
+  unsigned long count;
+  unsigned long next;
+} rgb_entry;
+
+typedef struct
+{
+  unsigned long len;
+  rgb_entry tbl[1];
+} rgb_hashtbl;
+
+#define hash(rgb,l) (((rgb).r*127+(rgb).g*997+(rgb).b*2111)&(l-1))
+#define same_col(rgb1,rgb2) \
+     (((rgb1).r==(rgb2).r) && ((rgb1).g==(rgb2).g) && ((rgb1).b==(rgb2).b))
+
+
+static INLINE int hash_enter(rgb_entry *rgbe,rgb_entry *end,
+			      int len,rgb_group rgb)
+{
+   register rgb_entry *re;
+
+   re=rgbe+hash(rgb,len);
+/*   fprintf(stderr,"%d\n",hash(rgb,len));*/
+
+   while (re->count && !same_col(re->rgb,rgb))
+      if (++re==end) re=rgbe;
+
+   if (!re->count)  /* allocate it */
+   {
+      re->rgb=rgb; 
+      re->count=1; 
+      return 1; 
+   }
+
+   re->count++;
+   return 0;
+}
+
+static INLINE int hash_enter_strip(rgb_entry *rgbe,rgb_entry *end,
+				   int len,rgb_group rgb,int strip)
+{
+   register rgb_entry *re;
+
+   unsigned char strip_r[24]=
+{ 0xff, 0xfe, 0xfe, 0xfe, 0xfc, 0xfc, 0xfc, 0xf8, 0xf8, 0xf8, 
+  0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80 };
+   unsigned char strip_g[24]=
+{ 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfc, 0xfc, 0xfc, 0xf8, 0xf8, 0xf8, 
+  0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80 };
+   unsigned char strip_b[24]=
+{ 0xfe, 0xfe, 0xfe, 0xfc, 0xfc, 0xfc, 0xf8, 0xf8, 0xf8, 
+  0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80 };
+
+   rgb.r&=strip_r[strip];
+   rgb.g&=strip_g[strip];
+   rgb.b&=strip_b[strip];
+
+   re=rgbe+hash(rgb,len);
+
+   while (re->count && !same_col(re->rgb,rgb))
+      if (++re==end) re=rgbe;
+
+   if (!re->count)  /* allocate it */
+   {
+      re->rgb=rgb; 
+      re->count=1; 
+      return 1; 
+   }
+
+   re->count++;
+   return 0;
+}
+
+static rgb_hashtbl *img_rehash(rgb_hashtbl *old, unsigned long newsize)
+{
+   unsigned long i;
+   rgb_hashtbl *new = malloc(sizeof(rgb_hashtbl) + 
+			     sizeof(rgb_entry) * newsize  );
+   MEMSET(new->tbl, 0, sizeof(rgb_entry) * newsize );
+  
+   if (!new) error("Out of memory\n");
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"img_rehash: old size = %lu, new size = %lu\n",
+	   (old ? old->len : 0), newsize);
+#endif
+  
+   new->len = newsize;
+   if (old)
+   {
+      for (i=0;i<old->len;i++)
+	 if (old->tbl[i].count)
+	 {
+	    register rgb_entry *re;
+
+	    re=new->tbl+hash(old->tbl[i].rgb,newsize);
+
+	    while (re->count)
+	       if (++re==new->tbl+newsize) re=new->tbl;
+
+	    *re=old->tbl[i];
+	 }
+      free(old);
+   }
+   return new;
+}
+
+static int cmp_red(rgb_entry *e1, rgb_entry *e2)
+{
+   return e1->rgb.r - e2->rgb.r;
+}
+
+static int cmp_green(rgb_entry *e1, rgb_entry *e2)
+{
+   return e1->rgb.g - e2->rgb.g;
+}
+
+static int cmp_blue(rgb_entry *e1, rgb_entry *e2)
+{
+   return e1->rgb.b - e2->rgb.b;
+}
+
+static int get_tbl_median(rgb_entry *tbl,int len)
+{
+   int a=0,x=0;
+   rgb_entry *m,*n;
+
+   len--;
+   m=tbl; n=tbl+len-1;
+   while (m<n)
+   {
+#if 0
+      fprintf(stderr,"pos %3d,%3d sum %3d low=%3d,%3d,%3dc%3d high=%3d,%3d,%3dc%3d\n",
+	      x,len,a,
+	      tbl[x].rgb.r,tbl[x].rgb.g,tbl[x].rgb.b,tbl[x].count, 
+	      tbl[len].rgb.r,tbl[len].rgb.g,tbl[len].rgb.b,tbl[len].count); 
+#endif
+      if (a>0) a-=m++->count; 
+      else a+=n--->count;
+   }
+   return m-tbl;
+}
+
+static rgb_group get_tbl_point(rgb_entry *tbl,int len)
+{
+   unsigned long r=0,g=0,b=0,n=0;
+   int x;
+   rgb_group rgb;
+   for (x=0; x<len; x++)
+   {
+      r+=((unsigned long)tbl[x].rgb.r)*tbl[x].count,
+      g+=((unsigned long)tbl[x].rgb.g)*tbl[x].count,
+      b+=((unsigned long)tbl[x].rgb.b)*tbl[x].count;
+      n+=tbl[x].count;
+#ifdef QUANT_DEBUG_POINT
+   fprintf(stderr,"(%d,%d,%d)*%lu; ",
+	   tbl[x].rgb.r,
+	   tbl[x].rgb.g,
+	   tbl[x].rgb.b,
+	   tbl[x].count);
+#endif
+   }
+   rgb.r=(unsigned char)(r/n);
+   rgb.g=(unsigned char)(g/n);
+   rgb.b=(unsigned char)(b/n);
+#ifdef QUANT_DEBUG_POINT
+   fprintf(stderr,"-> (%lu,%lu,%lu)/%lu = %d,%d,%d\n",
+	   r,g,b,n,
+	   rgb.r,rgb.g,rgb.b);
+#endif
+   return rgb;
+}
+
+#define MAKE_BINSORT(C,NAME) \
+   void NAME(rgb_entry *e, unsigned long len) \
+   { \
+      unsigned long pos[256];\
+      unsigned long i,j,k;\
+   \
+      rgb_entry *e2;\
+      e2=(rgb_entry*)xalloc(sizeof(rgb_entry)*len);\
+   \
+      for (i=0; i<256; i++) pos[i]=~0;\
+      \
+      for (i=0; i<len; i++)\
+      {\
+         e[i].next=pos[j=e[i].rgb.C];\
+         pos[j]=i;\
+      }\
+   \
+      for (i=j=0; i<256; i++)\
+      {\
+         k=pos[i];\
+         while (k!=(unsigned long)~0)\
+         {\
+   	 e2[j++]=e[k];\
+   	 k=e[k].next;\
+         }\
+      }\
+      MEMCPY(e,e2,len*sizeof(rgb_entry));\
+      free(e2);\
+   }
+
+MAKE_BINSORT(r,binsort_red)
+MAKE_BINSORT(g,binsort_green)
+MAKE_BINSORT(b,binsort_blue)
+
+
+static void sort_tbl(rgb_hashtbl *ht, 
+		     unsigned long start, unsigned long len,
+		     int level, unsigned long idx,
+		     unsigned long gap, int ldir,
+		     struct colortable *ct, rgb_group lower,rgb_group upper,
+		     unsigned long **rn_next,
+		     unsigned long *rgb_node)
+{
+   rgb_entry *tbl = ht->tbl;
+
+   int (*sortfun)(const void *, const void *);
+   unsigned long x,y;
+   int dir=0;
+
+#ifdef QUANT_DEBUG
+
+   fprintf(stderr,"%*ssort_tbl: level %d  start = %lu "
+	          " len=%lu, idx = %lu, gap = %lu",
+	   level, "",
+	   level, start, len, idx, gap);
+   fprintf(stderr,"\n%*s%d,%d,%d-%d,%d,%d ",level,"",
+	   lower.r,lower.g,lower.b,upper.r,upper.g,upper.b);
+
+#endif
+
+   if (len>1)
+   {
+   /* check which direction has the most span */
+   /* we make it easy for us, only three directions: r,g,b */
+
+
+#define PRIO_RED   2
+#define PRIO_GREEN 4
+#define PRIO_BLUE  1
+
+#if 0
+      rgb_group min,max;
+
+      max.r=min.r=tbl[start].rgb.r;
+      max.g=min.g=tbl[start].rgb.g;
+      max.b=min.b=tbl[start].rgb.b;
+
+      for (x=1; x<len; x++)
+      {
+	 if (min.r>tbl[start+x].rgb.r) min.r=tbl[start+x].rgb.r;
+	 if (min.g>tbl[start+x].rgb.g) min.g=tbl[start+x].rgb.g;
+	 if (min.b>tbl[start+x].rgb.b) min.b=tbl[start+x].rgb.b;
+	 if (max.r<tbl[start+x].rgb.r) max.r=tbl[start+x].rgb.r;
+	 if (max.g<tbl[start+x].rgb.g) max.g=tbl[start+x].rgb.g;
+	 if (max.b<tbl[start+x].rgb.b) max.b=tbl[start+x].rgb.b;
+      }
+#ifdef QUANT_DEBUG
+fprintf(stderr,"space: %d,%d,%d-%d,%d,%d  ",
+	min.r,min.g,min.b,
+	max.r,max.g,max.b);
+#endif
+
+      /* do this weighted, red=2 green=3 blue=1 */
+      if ((max.r-min.r)*PRIO_RED>(max.g-min.g)*PRIO_GREEN) /* r>g */
+	 if ((max.r-min.r)*PRIO_RED>(max.b-min.b)*PRIO_BLUE) /* r>g, r>b */
+	    dir=0;
+         else /* r>g, b>r */
+	    dir=2;
+      else
+	 if ((max.g-min.g)*PRIO_GREEN>(max.b-min.b)*PRIO_BLUE) /* g>r, g>b */
+	    dir=1;
+         else /* g>r, b>g */
+	    dir=2;
+#endif
+
+      rgbl_group sum={0,0,0};
+      rgb_group mid;
+
+      for (x=0; x<len; x++)
+      {
+	 sum.r+=tbl[start+x].rgb.r;
+	 sum.g+=tbl[start+x].rgb.g;
+	 sum.b+=tbl[start+x].rgb.b;
+      }
+      mid.r=(unsigned char)(sum.r/len);
+      mid.g=(unsigned char)(sum.g/len);
+      mid.b=(unsigned char)(sum.b/len);
+
+      sum.r=sum.g=sum.b=0;
+      for (x=0; x<len; x++)
+      {
+	 sum.r+=sq(tbl[start+x].rgb.r-mid.r);
+	 sum.g+=sq(tbl[start+x].rgb.g-mid.g);
+	 sum.b+=sq(tbl[start+x].rgb.b-mid.b);
+      }
+
+      if (sum.r*PRIO_RED>sum.g*PRIO_GREEN) /* r>g */
+	 if (sum.r*PRIO_RED>sum.b*PRIO_BLUE) /* r>g, r>b */
+	    dir=0;
+         else /* r>g, b>r */
+	    dir=2;
+      else
+	 if (sum.g*PRIO_GREEN>sum.b*PRIO_BLUE) /* g>r, g>b */
+	    dir=1;
+         else /* g>r, b>g */
+	    dir=2;
+
+      
+#ifdef QUANT_DEBUG
+      fprintf(stderr," dir=%d ",dir);
+#endif
+      if (dir!=ldir)
+	 switch (dir)
+	 {
+	    case 0: binsort_red(tbl+start,len); break;
+	    case 1: binsort_green(tbl+start,len); break;
+	    case 2: binsort_blue(tbl+start,len); break;
+	 }
+
+#ifdef QUANT_DEBUG
+      fprintf(stderr,"low: %d,%d,%d high: %d,%d,%d\n",
+	      tbl[start].rgb.r,
+	      tbl[start].rgb.g,
+	      tbl[start].rgb.b,
+	      tbl[start+len-1].rgb.r,
+	      tbl[start+len-1].rgb.g,
+	      tbl[start+len-1].rgb.b);
+#endif
+
+   }
+
+   if (len>1 && gap>1) /* recurse */
+   {
+      unsigned long pos,g1,g2;
+      rgb_group less,more,rgb;
+      unsigned char split_on=0;
+      int i;
+
+      pos=get_tbl_median(tbl+start,len);
+
+      rgb=tbl[start+pos].rgb;
+
+      less=upper;
+      more=lower;
+
+      switch (dir)
+      {
+	 case 0: more.r=rgb.r+1; split_on=less.r=rgb.r;
+                 while (pos<len-1 && tbl[start].rgb.r==tbl[start+pos].rgb.r) pos++;
+                 while (pos>0 && tbl[start+len-1].rgb.r==tbl[start+pos].rgb.r) pos--;
+                 break;
+	 case 1: more.g=rgb.g+1; split_on=less.g=rgb.g;
+                 while (pos<len-1 && tbl[start].rgb.g==tbl[start+pos].rgb.g) pos++;
+                 while (pos>0 && tbl[start+len-1].rgb.g==tbl[start+pos].rgb.g) pos--;
+                 break;
+	 case 2: more.b=rgb.b+1; split_on=less.b=rgb.b;
+                 while (pos<len-1 && tbl[start].rgb.b==tbl[start+pos].rgb.b) pos++;
+                 while (pos>0 && tbl[start+len-1].rgb.b==tbl[start+pos].rgb.b) pos--;
+                 break;
+      }
+
+#ifdef QUANT_DEBUG
+      fprintf(stderr, " pos=%lu len=%lu\n",pos,len);
+#endif
+
+#ifdef QUANT_DEBUG_GAP
+
+      fprintf(stderr,"\n left: ");
+      for (i=0; i<=pos; i++) 
+	 fprintf(stderr,"%d,%d,%d; ",
+		 tbl[start+i].rgb.r,tbl[start+i].rgb.g,tbl[start+i].rgb.b);
+      fprintf(stderr,"\n right: ");
+      for (; i<len; i++) 
+	 fprintf(stderr,"%d,%d,%d; ",
+		 tbl[start+i].rgb.r,tbl[start+i].rgb.g,tbl[start+i].rgb.b);
+      fprintf(stderr,"\n");
+
+#endif		
+      g1=gap>>1; 
+
+#ifdef QUANT_DEBUG_GAP
+      fprintf(stderr,"gap: %d / %d pos+1=%d len-pos-1=%d gap-g1=%d\n",
+	      g1,gap-g1,pos+1,len-pos-1,gap-g1);
+#endif
+
+      if (pos+1<g1) g1=pos+1,g2=gap-g1;
+      else if (len-pos-1<gap-g1) g2=(len-pos-1),g1=gap-g2;
+      else g2=gap-g1;
+
+#ifdef QUANT_DEBUG
+      fprintf(stderr,"gap: %d / %d  ",g1,g2);
+#endif
+
+      if (gap>1)
+      {
+	 /* split tree */
+
+	 *rgb_node=
+	    ( (*rn_next)-ct->rgb_node )
+	    | ( ((unsigned long)split_on) << 22 )
+	    | ( (dir+1)<<30 ) & 0xffffffff;
+	 rgb_node=*rn_next;
+	 (*rn_next)+=2;
+
+	 sort_tbl(ht,start,pos+1,
+		  level+1,idx,g1,dir,
+		  ct,lower,less,rn_next,rgb_node++);
+	 
+	 sort_tbl(ht,start+pos+1,len-pos-1,
+		  level+1,idx+g1,g2,dir,
+		  ct,more,upper,rn_next,rgb_node);
+      }
+      else
+      {
+	 /* this shouldn't occur? /law */
+	 abort();
+	 sort_tbl(ht,start,pos+1,
+		  level+1,idx,g1,dir,
+		  ct,lower,less,rn_next,rgb_node);
+      }
+      return;
+   }
+   else 
+   {
+      int r,g,b;
+      if (len>1)
+	 ct->clut[idx]=get_tbl_point(tbl+start,len);
+      else
+	 ct->clut[idx]=tbl[start].rgb;
+
+#ifdef QUANT_DEBUG
+      fprintf(stderr,"-> end node [%d,%d,%d] - [%d,%d,%d] => %d,%d,%d\n",
+	      lower.r, lower.g, lower.b, upper.r, upper.g, upper.b,
+	      ct->clut[idx].r,ct->clut[idx].g,ct->clut[idx].b);
+#endif      
+      
+      /* write end node */
+      *rgb_node=idx; /* done */
+   }
+}
+
+static struct colortable *colortable_allocate(int numcol)
+{
+   struct colortable *ct;
+   ct = malloc(sizeof(struct colortable)+
+	       sizeof(rgb_group)*numcol);
+   if (!ct) error("Out of memory.\n");
+   MEMSET(ct,0,sizeof(struct colortable)+
+	       sizeof(rgb_group)*numcol);
+   ct->numcol=numcol;
+   ct->rgb_node=malloc(sizeof(unsigned long)*numcol*4);
+   MEMSET(ct->rgb_node,0,
+	  sizeof(unsigned long)*numcol*4);
+   return ct;
+}
+
+struct colortable *colortable_quant(struct image *img,int numcol)
+{
+   rgb_hashtbl *tbl;
+   INT32 i,j;
+   INT32 sz = img->xsize * img->ysize;
+   rgb_entry entry;
+   struct colortable *ct=NULL;
+   rgb_group black,white;
+   rgb_group *p;
+   INT32 entries=0;
+   unsigned long *next_free_rgb_node;
+
+   THREADS_ALLOW();
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"img_quant called\n");
+#endif
+   CHRONO("quant");
+
+   if (numcol<2) numcol=2;
+
+   if (numcol>MAX_NUMCOL) numcol=MAX_NUMCOL; 
+
+   ct = colortable_allocate(numcol);
+
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"Moving colors into hashtable\n");
+#endif
+
+   for (;;)
+   {
+      int strip=0;
+rerun:
+      entries=0;
+
+
+      CHRONO("hash init");
+      
+      tbl = img_rehash(NULL, 8192);
+      
+      p=img->img;
+      i=sz;
+      do
+      {
+	 register rgb_entry *rgbe,*end;
+	 int len,trig;
+
+#ifdef QUANT_DEBUG
+	 fprintf(stderr,"hash: %d pixels left...\n",i);
+#endif
+	 CHRONO("hash...");
+	
+	 len=tbl->len;
+	 trig=(len*2)/10; /* 20% full => rehash bigger */
+	 end=(rgbe=tbl->tbl)+tbl->len;
+
+	 if (!strip)
+	 {
+	    while (i--)
+	       if ( (entries+=hash_enter(rgbe,end,len,*(p++))) > trig )
+	       {
+#ifdef QUANT_DEBUG
+		  fprintf(stderr,"rehash: 20%% = %d / %d...\n",entries,len);
+#endif
+		  CHRONO("rehash...");
+		  if ((len<<2) > QUANT_MAXIMUM_NUMBER_OF_COLORS)
+		  {
+		     strip++;
+		     fprintf(stderr,"strip: %d\n",strip);
+		     free(tbl);
+		     goto rerun;
+		  }
+		  tbl=img_rehash(tbl,len<<2); /* multiple size by 4 */
+		  break;
+	       }
+	 }
+	 else
+	    while (i--)
+	       if ( (entries+=hash_enter_strip(rgbe,end,len,*(p++),strip)) > trig )
+	       {
+#ifdef QUANT_DEBUG
+		  fprintf(stderr,"rehash: 20%% = %d / %d...\n",entries,len);
+#endif
+		  CHRONO("rehash...");
+		  if ((len<<2) > QUANT_MAXIMUM_NUMBER_OF_COLORS)
+		  {
+		     strip++;
+		     free(tbl);
+		     goto rerun;
+		  }
+		  tbl=img_rehash(tbl,len<<2); /* multiple size by 4 */
+		  break;
+	       }
+      }
+      while (i>=0);
+      break;
+   }
+     
+   /* Compact the hash table */
+   CHRONO("compact");
+
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"Compacting\n");
+#endif
+   i = tbl->len - 1;
+   j = 0;
+   while (i > entries)
+   {
+      while ((i >= entries) && tbl->tbl[i].count == 0) i--;
+      while ((j <  entries) && tbl->tbl[j].count != 0) j++;
+      if (j<i)
+      {
+	 tbl->tbl[j] = tbl->tbl[i];
+	 tbl->tbl[i].count = 0;
+      }
+   }
+
+   white.r=white.g=white.b=255;
+   black.r=black.g=black.b=0;
+
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"%d colors found, sorting and quantizing...\n",j);
+#endif
+
+   CHRONO("sort");
+   if (j<numcol) ct->numcol=numcol=j;
+
+   next_free_rgb_node=ct->rgb_node+1;
+   sort_tbl(tbl, 0, j, 0, 0, numcol, -1, ct, black, white,
+	    &next_free_rgb_node,ct->rgb_node);
+
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"img_quant done, %d colors selected\n", numcol);
+#endif
+   CHRONO("done");
+
+   free(tbl);
+   CHRONO("really done");
+   THREADS_DISALLOW();
+   return ct;
+}
+
+
+struct colortable *colortable_from_array(struct array *arr,char *from)
+{
+   rgb_hashtbl *tbl;
+   INT32 i,j;
+   struct colortable *ct=NULL;
+   rgb_group black,white;
+   rgb_group *p;
+   INT32 entries=0;
+   struct svalue s,s2;
+   unsigned long *next_free_rgb_node;
+
+   THREADS_ALLOW();
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"ctfa called\n");
+#endif
+   CHRONO("ctfa");
+
+   white.r=white.g=white.b=255;
+   black.r=black.g=black.b=0;
+
+   tbl=img_rehash(NULL,arr->size);
+  
+   s2.type=s.type=T_INT;
+   for (i=0; i<arr->size; i++)
+   {
+      array_index(&s,arr,i);
+      if (s.type!=T_ARRAY || s.u.array->size<3)
+      {
+	 free(tbl);
+	 error("Illegal type in colorlist, element %d, %s\n",i,from);
+      }
+      array_index(&s2,s.u.array,0);
+      if (s2.type!=T_INT) tbl->tbl[i].rgb.r=0; else tbl->tbl[i].rgb.r=s2.u.integer;
+      array_index(&s2,s.u.array,1);
+      if (s2.type!=T_INT) tbl->tbl[i].rgb.g=0; else tbl->tbl[i].rgb.g=s2.u.integer;
+      array_index(&s2,s.u.array,2);
+      if (s2.type!=T_INT) tbl->tbl[i].rgb.b=0; else tbl->tbl[i].rgb.b=s2.u.integer;
+      tbl->tbl[i].count=1;
+   }
+   free_svalue(&s);
+   free_svalue(&s2);
+
+   ct = colortable_allocate(arr->size);
+
+   CHRONO("sort");
+   next_free_rgb_node=ct->rgb_node+1;
+   sort_tbl(tbl, 0, arr->size, 0, 0, arr->size, -1, ct, black, white,
+	    &next_free_rgb_node,ct->rgb_node);
+
+#ifdef QUANT_DEBUG
+   fprintf(stderr,"img_quant done, %d colors selected\n", arr->size);
+#endif
+   CHRONO("done");
+
+   free(tbl);
+
+   for (i=0; i<QUANT_SELECT_CACHE; i++)
+      ct->cache[i].index=white;
+   j=colortable_rgb(ct,black); /* ^^ dont find it in the cache... */
+   for (i=0; i<QUANT_SELECT_CACHE; i++)
+      ct->cache[i].index=black,
+	 ct->cache[i].value=j;
+
+   CHRONO("really done");
+   THREADS_DISALLOW();
+   return ct;
+}
+
+
+int colortable_rgb(struct colortable *ct,rgb_group rgb)
+{
+   int i,best;
+
+   if (ct->cache->index.r==rgb.r &&
+       ct->cache->index.g==rgb.g &&
+       ct->cache->index.b==rgb.b) 
+      return ct->cache->value;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr,"rgb: %d,%d,%d\n",rgb.r,rgb.g,rgb.b);
+#endif
+
+#if QUANT_SELECT_CACHE>1
+   for (i=1; i<QUANT_SELECT_CACHE; i++)
+      if (ct->cache[i].index.r==rgb.r &&
+	  ct->cache[i].index.g==rgb.g &&
+	  ct->cache[i].index.b==rgb.b) 
+      {
+	 best=ct->cache[i].value;
+
+	 MEMMOVE(ct->cache+1,ct->cache,
+		 i*sizeof(struct rgb_cache));
+	 ct->cache[0].index=rgb;
+	 ct->cache[0].value=best;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr,"cache: %lu: %d,%d,%d\n",best,ct->clut[best].r,ct->clut[best].g,ct->clut[best].b);
+#endif
+	 return best;
+      }
+#endif
+
+   /* find node */
+
+#if 1
+
+   do 
+   {
+      rgb_group min={0,0,0},max={255,255,255};
+      unsigned long *rn;
+      unsigned char split;
+
+      rn=ct->rgb_node;
+
+      for (;;)
+      {
+#ifdef QUANT_DEBUG_RGB
+      fprintf(stderr,"-> %d: %c%d %d\n",
+	      rn-ct->rgb_node,
+	      (((*rn)>>30)==0)?'c':
+	      (((*rn)>>30)==1)?'r':
+	      (((*rn)>>30)==2)?'g':'b',
+	      ((*rn)>>22) & 255,
+	      ((*rn)&4194303));
+#endif
+
+	 switch ((*rn)>>30)
+	 {
+	    case 0: /* end node */ break;
+	    case 1: /* red */
+	       split=(unsigned char)( ((*rn)>>22) & 255 );
+	       rn=ct->rgb_node+((*rn)&4194303);
+	       if (rgb.r<=split) max.r=split;
+	       else rn++,min.r=split+1;
+	       continue;
+	    case 2: /* green */
+	       split=(unsigned char)( ((*rn)>>22) & 255 );
+	       rn=ct->rgb_node+((*rn)&4194303);
+	       if (rgb.g<=split) max.g=split;
+	       else rn++,min.g=split+1;
+	       continue;
+	    case 3: /* blue */
+	       split=(unsigned char)( ((*rn)>>22) & 255 );
+	       rn=ct->rgb_node+((*rn)&4194303);
+	       if (rgb.b<=split) max.b=split;
+	       else rn++,min.b=split+1;
+	       continue;
+	 }
+	 break;
+      }
+      best=*rn;
+   } 
+   while (0);
+
+#endif
+
+   /* place in cache */
+#if QUANT_SELECT_CACHE>1
+   MEMMOVE(ct->cache+1,ct->cache,
+	   (QUANT_SELECT_CACHE-1)*sizeof(struct rgb_cache));
+#endif
+   ct->cache[0].index=rgb;
+   ct->cache[0].value=best;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr," -> %lu: %d,%d,%d\n",best,
+	ct->clut[best].r,ct->clut[best].g,ct->clut[best].b);
+#endif
+   return best;
+}
+
+int colortable_rgb_nearest(struct colortable *ct,rgb_group rgb)
+{
+   int i,best=0,di,di2;
+   rgb_group *prgb;
+
+   if (ct->cache->index.r==rgb.r &&
+       ct->cache->index.g==rgb.g &&
+       ct->cache->index.b==rgb.b) 
+      return ct->cache->value;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr,"rgb_n: #%02x%02x%02x;  ",rgb.r,rgb.g,rgb.b);
+#endif
+
+#if QUANT_SELECT_CACHE>1
+   for (i=1; i<QUANT_SELECT_CACHE; i++)
+      if (ct->cache[i].index.r==rgb.r &&
+	  ct->cache[i].index.g==rgb.g &&
+	  ct->cache[i].index.b==rgb.b) 
+      {
+	 best=ct->cache[i].value;
+
+	 MEMMOVE(ct->cache+1,ct->cache,
+		 i*sizeof(struct rgb_cache));
+	 ct->cache[0].index=rgb;
+	 ct->cache[0].value=best;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr,"cache: %lu: %d,%d,%d\n",best,ct->clut[best].r,ct->clut[best].g,ct->clut[best].b);
+#endif
+	 return best;
+      }
+#endif
+
+   /* find node */
+
+   di=1000000L;
+   for (i=0; i<ct->numcol; i++)
+   {
+      prgb=ct->clut+i;
+      if ((di2=DISTANCE(*prgb,rgb))<di) 
+      { 
+	 best=i; 
+	 di=di2;
+#ifdef QUANT_DEBUG_RGB
+	 fprintf(stderr,"#%02x%02x%02x (%d) (%d), ",
+		 ct->clut[i].r,
+		 ct->clut[i].g,
+		 ct->clut[i].b,
+		 i,di);
+#endif
+      }
+   }
+#ifdef QUANT_DEBUG_RGB
+   fprintf(stderr,"\n");
+#endif
+
+   /* place in cache */
+#if QUANT_SELECT_CACHE>1
+   MEMMOVE(ct->cache+1,ct->cache,
+	   (QUANT_SELECT_CACHE-1)*sizeof(struct rgb_cache));
+#endif
+   ct->cache[0].index=rgb;
+   ct->cache[0].value=best;
+
+#ifdef QUANT_DEBUG_RGB
+fprintf(stderr," -> %lu: %d,%d,%d\n",best,
+	ct->clut[best].r,ct->clut[best].g,ct->clut[best].b);
+#endif
+   return best;
+}
+
+void colortable_free(struct colortable *ct)
+{
+   int r,g,b;
+   free((char *)ct->rgb_node);
+   free((char *)ct);
+}
diff --git a/src/modules/Image/testfont b/src/modules/Image/testfont
new file mode 100644
index 0000000000000000000000000000000000000000..3705501ac48434af2a765ba61eee5a6718d3540e
Binary files /dev/null and b/src/modules/Image/testfont differ
diff --git a/src/modules/Image/testsuite.in b/src/modules/Image/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..b8a35edf849d18b3b0ca44ca915ed7063d3b1e7a
--- /dev/null
+++ b/src/modules/Image/testsuite.in
@@ -0,0 +1,52 @@
+cond([[ master()->programs["/precompiled/image"] ]],
+[[
+  test_true(programp(Image))
+  test_true(objectp(clone(Image)))
+  test_true(objectp(clone(Image,10,10)))
+  test_true(objectp(clone(Image,10,10,1,1,1)))
+  test_eq(clone(Image,10,12)->xsize(),10))
+  test_eq(clone(Image,10,12)->ysize(),12))
+  test_true(objectp(clone(Image,10,10,1,1,1)->copy()))
+  test_eq(clone(Image,10,12)->copy()->xsize(),10))
+  test_eq(clone(Image,10,12)->copy()->ysize(),12))
+dnl  test_true(objectp(clone(Image,10,10)->crop(2,2,3,3)))
+  test_true(objectp(clone(Image,10,10)->autocrop()))
+  test_true(objectp(clone(Image,10,10)->gray()))
+  test_true(objectp(clone(Image,10,10)->color(2,2,4)))
+  test_true(objectp(clone(Image,10,10)->invert()))
+  test_true(objectp(clone(Image,10,10)->threshold(10,20,30)))
+dnl matrix
+  test_true(objectp(clone(Image,10,10)->scale(2.1)))
+  test_eq(clone(Image,10,12)->scale(2.0)->xsize(),20)
+  test_eq(clone(Image,10,12)->scale(2.0)->ysize(),24)
+  test_true(objectp(clone(Image,10,10)->scale(0.2)))
+  test_true(objectp(clone(Image,10,10)->scale(2.0,0.2)))
+  test_true(objectp(clone(Image,10,10)->scale(0.2,2.2)))
+  test_true(objectp(clone(Image,10,10)->scale(0.2,2.2)))
+  test_true(objectp(clone(Image,10,10)->scale(0.2,2.2)))
+  test_eq(clone(Image,10,10)->scale(33,57)->xsize(),33)
+  test_eq(clone(Image,10,10)->scale(33,57)->ysize(),57)
+  test_true(stringp(clone(Image,10,10)->toppm()))
+dnl fromppm
+  test_true(stringp(clone(Image,10,10)->toppm()))
+  test_true(stringp(clone(Image,10,10)->togif()))
+  test_true(objectp(clone(Image,10,10)->paste(clone(Image,3,3),2,3)))
+dnl past_alpha
+dnl past_mask
+  test_do(clone(Image,10,10)->setcolor(2,2,2))
+  test_do(clone(Image,10,10)->setpixel(2,2,2,2,2))
+  test_do(clone(Image,10,10)->line(2,2,4,4,2,2,2))
+  test_do(clone(Image,10,10)->box(2,2,4,4,2,2,2))
+  test_do(clone(Image,10,10)->circle(2,2,4,4,2,2,2))
+dnl tuned_box
+  test_eq(clone(Image,10,10)->xsize(),10)
+  test_eq(clone(Image,10,10)->ysize(),10)
+  test_do(clone(Font))
+  test_do(clone(Font)->load("SRCDIR/testfont"))
+  test_any(object o=clone(Font); o->load("SRCDIR/testfont"); return o->height(),19)
+  test_any(object o=clone(Font); o->load("SRCDIR/testfont"); return o->write("foo")->xsize(),23)
+  test_any(object o=clone(Font); o->load("SRCDIR/testfont"); return
+o->write("foo")->ysize(),20)
+dnl  test_any(object o=clone(Font); o->load("SRCDIR/testfont"); o->write("foo"); clone(Image,100,100)->paste_alpha(o); return 1,1)
+]])
+
diff --git a/src/modules/Image/togif.c b/src/modules/Image/togif.c
new file mode 100644
index 0000000000000000000000000000000000000000..7cde3f906c843b22c0946e4b6aba98d50bc95586
--- /dev/null
+++ b/src/modules/Image/togif.c
@@ -0,0 +1,778 @@
+/*
+
+togif 
+
+Pontus Hagland, law@infovav.se
+
+$Id: togif.c,v 1.1 1997/02/11 08:35:47 hubbe Exp $ 
+
+*/
+
+#include "global.h"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "stralloc.h"
+#include "global.h"
+#include "threads.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 "dynamic_buffer.h"
+
+#include "image.h"
+#include "lzw.h"
+
+#define INITIAL_BUF_LEN 8192
+
+#define THIS ((struct image *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+#if 0
+#include <sys/resource.h>
+#define CHRONO(X) chrono(X)
+
+static void chrono(char *x)
+{
+   struct rusage r;
+   static struct rusage rold;
+   getrusage(RUSAGE_SELF,&r);
+   fprintf(stderr,"%s: %ld.%06ld - %ld.%06ld\n",x,
+	   r.ru_utime.tv_sec,r.ru_utime.tv_usec,
+
+	   ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?-1:0)
+	   +r.ru_utime.tv_sec-rold.ru_utime.tv_sec,
+           ((r.ru_utime.tv_usec-rold.ru_utime.tv_usec<0)?1000000:0)
+           + r.ru_utime.tv_usec-rold.ru_utime.tv_usec
+	   );
+
+   rold=r;
+}
+#else
+#define CHRONO(X)
+#endif
+
+
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)<(b)?(b):(a))
+#define testrange(x) max(min((x),255),0)
+
+static void buf_word( unsigned short w, dynamic_buffer *buf )
+{
+   low_my_putchar( w&0xff, buf );
+   low_my_putchar( (w>>8)&0xff, buf );
+}
+
+#define WEIGHT_NEXT(X) (((X)*8)/20)
+#define WEIGHT_DOWNNEXT(X) (((X)*3)/20)
+#define WEIGHT_DOWN(X) (((X)*3)/20)
+#define WEIGHT_DOWNBACK(X) (((X)*0)/20)
+
+static int floyd_steinberg_add(rgbl_group *errl,
+			       rgbl_group *errlfwd,
+			       rgbl_group *errlback,
+			       rgbl_group *err,
+			       rgb_group rgb,
+			       struct colortable *ct)
+{
+   rgb_group rgb2,rgb3;
+   rgbl_group cerr;
+   int c;
+   rgb2.r=testrange((long)rgb.r+err->r/FS_SCALE);
+   rgb2.g=testrange((long)rgb.g+err->g/FS_SCALE);
+   rgb2.b=testrange((long)rgb.b+err->b/FS_SCALE);
+#ifdef FS_DEBUG
+   fprintf(stderr,"%g,%g,%g+%g,%g,%g=%g,%g,%g ",
+	   1.0*rgb.r, 1.0*rgb.g, 1.0*rgb.b, 
+	   err->r*1.0/FS_SCALE, err->g*1.0/FS_SCALE, err->b*1.0/FS_SCALE,
+	   rgb2.r*1.0, rgb2.g*1.0, rgb2.b*1.0);
+#endif
+   c=colortable_rgb(ct,rgb2);
+   rgb3=ct->clut[c];
+   cerr.r=(long)rgb.r*FS_SCALE-(long)rgb3.r*FS_SCALE+err->r;
+   cerr.g=(long)rgb.g*FS_SCALE-(long)rgb3.g*FS_SCALE+err->g;
+   cerr.b=(long)rgb.b*FS_SCALE-(long)rgb3.b*FS_SCALE+err->b;
+
+#ifdef FS_DEBUG
+   fprintf(stderr,"got %g,%g,%g err %g,%g,%g ",
+	   1.0*rgb3.r, 
+	   1.0*rgb3.g, 
+	   1.0*rgb3.b, 
+	   1.0*cerr.r,
+	   1.0*cerr.g,
+	   1.0*cerr.b);
+#endif
+
+   errl->r+=WEIGHT_DOWN(cerr.r);
+   errl->g+=WEIGHT_DOWN(cerr.g);
+   errl->b+=WEIGHT_DOWN(cerr.b);
+   if (errlback)
+   {
+      errlback->r+=WEIGHT_DOWNBACK(cerr.r);
+      errlback->g+=WEIGHT_DOWNBACK(cerr.g);
+      errlback->b+=WEIGHT_DOWNBACK(cerr.b);
+#ifdef FS_DEBUG
+      fprintf(stderr,"errlback=>%g ",errlback->g*1.0/FS_SCALE);
+#endif
+   }
+   if (errlfwd)
+   {
+      err->r=WEIGHT_NEXT(cerr.r);
+      err->g=WEIGHT_NEXT(cerr.g);
+      err->b=WEIGHT_NEXT(cerr.b);
+      err->r+=errlfwd->r;
+      err->g+=errlfwd->g;
+      err->b+=errlfwd->b;
+      errlfwd->r=WEIGHT_DOWNNEXT(cerr.r);
+      errlfwd->g=WEIGHT_DOWNNEXT(cerr.g);
+      errlfwd->b=WEIGHT_DOWNNEXT(cerr.b);
+#ifdef FS_DEBUG
+      fprintf(stderr,"errlfwd=>%g ",errlfwd->g*1.0/FS_SCALE);
+#endif
+   }
+#ifdef FS_DEBUG
+   fprintf(stderr,"errl=>%g ",errl->g*1.0/FS_SCALE);
+   fprintf(stderr,"err=>%g\n",err->g*1.0/FS_SCALE);
+#endif
+   return c;
+}
+
+void image_floyd_steinberg(rgb_group *rgb,int xsize,
+			   rgbl_group *errl,
+			   int way,int *res,
+			   struct colortable *ct)
+{
+   rgbl_group err;
+   int x;
+
+   if (way)
+   {
+      err.r=errl[xsize-1].r;
+      err.g=errl[xsize-1].g;
+      err.b=errl[xsize-1].b;
+      for (x=xsize-1; x>=0; x--)
+	 res[x]=floyd_steinberg_add(errl+x,
+				    (x==0)?NULL:errl+x-1,
+				    (x==xsize-1)?NULL:errl+x+1,
+				    &err,rgb[x],ct);
+   }
+   else
+   {
+      err.r=errl->r;
+      err.g=errl->g;
+      err.b=errl->b;
+      for (x=0; x<xsize; x++)
+	 res[x]=floyd_steinberg_add(errl+x,
+				    (x==xsize-1)?NULL:errl+x+1,
+				    (x==0)?NULL:errl+x-1,
+				    &err,rgb[x],ct);
+   }
+}
+		     
+
+struct pike_string *
+   image_encode_gif(struct image *img,struct colortable *ct,
+		    rgb_group *transparent,int fs)
+{
+   dynamic_buffer buf;
+   long i;
+   rgb_group *rgb;
+   struct lzw lzw;
+   int colors,bpp;
+
+CHRONO("image_encode_gif begin");
+   
+   buf.s.str=NULL;
+   initialize_buf(&buf);
+
+   colors=4; bpp=2;
+   while (colors<ct->numcol) { colors<<=1; bpp++; }
+
+   low_my_binary_strcat(transparent?"GIF89a":"GIF87a",6,&buf);
+   buf_word((unsigned short)img->xsize,&buf);
+   buf_word((unsigned short)img->ysize,&buf);
+   low_my_putchar( (char)(0xf0|(bpp-1)), &buf);
+   /* | global colormap | 3 bits color res | sort | 3 bits bpp */
+   /* color res is'nt cared of */
+
+   low_my_putchar( 0, &buf ); /* background color */
+   low_my_putchar( 0, &buf ); /* just zero */
+
+   for (i=0; i<ct->numcol; i++)
+   {
+      low_my_putchar(ct->clut[i].r,&buf);
+      low_my_putchar(ct->clut[i].g,&buf);
+      low_my_putchar(ct->clut[i].b,&buf);
+   }
+
+   for (; i<colors; i++)
+   {
+      low_my_putchar(0,&buf);
+      low_my_putchar(0,&buf);
+      low_my_putchar(0,&buf);
+   }
+
+   if (transparent)
+   {
+      i=colortable_rgb(ct,*transparent);
+
+      low_my_putchar( '!', &buf );  /* extras */
+      low_my_putchar( 0xf9, &buf ); /* transparency */
+      low_my_putchar( 4, &buf );
+      low_my_putchar( 1, &buf );
+      low_my_putchar( 0, &buf );
+      low_my_putchar( 0, &buf );
+      low_my_putchar( i, &buf );
+      low_my_putchar( 0, &buf );
+   }
+
+
+   low_my_putchar( ',', &buf ); /* image separator */
+
+   buf_word(0,&buf); /* leftofs */
+   buf_word(0,&buf); /* topofs */
+   buf_word(img->xsize,&buf); /* width */
+   buf_word(img->ysize,&buf); /* height */
+
+   low_my_putchar(0x00, &buf); 
+      /* not interlaced (interlaced == 0x40) */
+      /* no local colormap ( == 0x80) */
+
+   low_my_putchar( bpp, &buf ); /* bits per pixel , or min 2 */
+   
+   i=img->xsize*img->ysize;
+   rgb=img->img;
+
+CHRONO("image_encode_gif header done");
+
+THREADS_ALLOW();
+   lzw_init(&lzw,bpp);
+   if (!fs)
+      while (i--) lzw_add(&lzw,colortable_rgb(ct,*(rgb++)));
+   else
+   {
+      rgbl_group *errb;
+      rgb_group corgb;
+      int w,*cres,j;
+      errb=(rgbl_group*)xalloc(sizeof(rgbl_group)*img->xsize);
+      cres=(int*)xalloc(sizeof(int)*img->xsize);
+      for (i=0; i<img->xsize; i++)
+	errb[i].r=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+	errb[i].g=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+	errb[i].b=(rand()%(FS_SCALE*2+1))-FS_SCALE;
+
+      w=0;
+      i=img->ysize;
+      while (i--)
+      {
+	 image_floyd_steinberg(rgb,img->xsize,errb,w=!w,cres,ct);
+	 for (j=0; j<img->xsize; j++)
+	    lzw_add(&lzw,cres[j]);
+	 rgb+=img->xsize;
+      }
+
+      free(errb);
+      free(cres);
+   }
+
+   lzw_write_last(&lzw);
+
+CHRONO("lzw done");
+
+   for (i=0; i<(int)lzw.outpos; i+=254)
+   {
+      int wr;
+      if (i+254>(int)lzw.outpos) wr=lzw.outpos-i;
+      else wr=254;
+      low_my_putchar( (unsigned char)wr, &buf ); /* bytes in chunk */
+      low_my_binary_strcat( (char *) lzw.out+i, wr, &buf );
+   }
+   low_my_putchar( 0, &buf ); /* terminate stream */
+
+CHRONO("image_encode_gif wrote ok");
+
+   lzw_quit(&lzw);
+
+   low_my_putchar( ';', &buf ); /* end gif file */
+
+CHRONO("image_encode_gif done");
+THREADS_DISALLOW();
+
+   return low_free_buf(&buf);
+}
+
+#define STD_ARENA_SIZE 16384
+
+int image_decode_gif(struct image *dest,struct image *dest_alpha,
+		     unsigned char *src,unsigned long len)
+{
+   unsigned char *arena,*tmpstore,*q;
+   rgb_group *global_palette,*palette;
+   rgb_group *rgb;
+   int bpp;
+   unsigned long i,j;
+   INT32 mod;
+   INT32 leftofs,topofs,width,height;
+   int interlaced,transparent;
+   unsigned long arenalen,pos;
+
+   if (src[0]!='G'
+       ||src[1]!='I'
+       ||src[2]!='F'
+       ||len<12) 
+      return 0; /* not a gif, you fool */
+
+   dest->xsize=src[6]+(src[7]<<8);
+   dest->ysize=src[8]+(src[9]<<8);
+
+   if (! (arena=malloc(arenalen=STD_ARENA_SIZE)) ) return 0;
+
+   dest->img=malloc(dest->xsize*dest->ysize*sizeof(rgb_group));
+   if (!dest->img) { free(arena); return 0; }
+      /* out of memory (probably illegal size) */
+
+   bpp=(src[10]&7)+1;
+
+   THREADS_ALLOW();
+   if (src[10]&128)
+   {
+      global_palette=(rgb_group*)(src+13);
+      src+=3*(1<<bpp);
+      len-=3*(1<<bpp);
+      rgb=dest->img;
+      i=dest->xsize*dest->ysize;
+/*
+      while (i--)
+	 *rgb=global_palette[src[11]]; * paint with background color */
+
+   }
+   else 
+      global_palette=NULL;
+   MEMSET(dest->img,0,sizeof(rgb_group)*dest->xsize*dest->ysize);
+   src+=13; len-=13;
+   
+   do
+   {
+      switch (*src)
+      {
+	 case '!': /* function block */
+	    if (len<7) break; /* no len left */
+	    if (src[1]==0xf9) 
+	       transparent=src[6];
+	    len-=src[3]+1;
+	    src+=src[3]+1;
+	    continue;
+ 	 case ',': /* image block(s) */
+	    if (len<10) len-=10; /* no len left */
+	    leftofs=src[1]+(src[2]<<8);
+	    topofs=src[3]+(src[4]<<8);
+	    width=src[5]+(src[6]<<8);
+	    height=src[7]+(src[8]<<8);
+	    interlaced=src[9]&64;
+	    
+	    if (src[9]&128)
+	    {
+	       palette=(rgb_group*)(src+10);
+	       src+=((src[9]&7)+1)*3;
+	    }
+	    else
+	       palette=global_palette;
+
+	    src+=11;
+	    len-=11;
+	    pos=0;
+	    if (len<3) break; /* no len left */
+	    bpp=src[-1];
+
+	    while (len>1)
+	    {
+	       if (!(i=*src)) break; /* length of block */
+	       if (pos+i>arenalen)
+	       {
+		  arena=realloc(arena,arenalen*=2);
+		  if (!arena) return 1;
+	       }
+	       MEMCPY(arena+pos,src+1,min(i,len-1));
+	       pos+=min(i,len-1);
+	       len-=i+1;
+	       src+=i+1;
+	    }
+
+	    if (leftofs+width<=dest->xsize && topofs+height<=dest->ysize)
+	    {
+	       tmpstore=malloc(width*height);
+	       if (!tmpstore) break;
+	       i=lzw_unpack(tmpstore,width*height,arena,pos,bpp);
+	       if (i!=(unsigned long)(width*height))
+		  MEMSET(tmpstore+i,0,width*height-i);
+	       rgb=dest->img+leftofs+topofs*dest->ysize;
+	       mod=width-dest->xsize;
+	       j=height;
+	       q=tmpstore;
+	       if (palette)
+		  while (j--)
+		  {
+		     i=width;
+		     while (i--) *(rgb++)=palette[*(q++)];
+		     rgb+=mod;
+		  }
+	       free(tmpstore);
+	    }
+	    
+	    continue;
+	 case ';':
+	    
+	    break; /* file done */
+	 default:
+	    break; /* unknown, file is ok? */
+      }
+   }
+   while (0);
+   THREADS_DISALLOW();
+
+   if (arena) free(arena);
+   return 1; /* ok */
+}
+
+
+
+void image_fromgif(INT32 args)
+{
+   if (sp[-args].type!=T_STRING)
+      error("Illegal argument to image->fromgif()\n");
+
+   if (THIS->img) free(THIS->img);
+   THIS->img=NULL;
+
+   image_decode_gif(THIS,NULL,sp[-args].u.string->str,sp[-args].u.string->len);
+
+   pop_n_elems(args);
+   THISOBJ->refs++;
+   push_object(THISOBJ);
+}
+
+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;
+}
+
+void image_togif(INT32 args)
+{
+   rgb_group *transparent=NULL;
+   struct colortable *ct=NULL;
+
+   if (args>0 && sp[-args].type==T_ARRAY)
+      ct=colortable_from_array(sp[-args].u.array,"image->togif()\n");
+   else if (args>0 && args!=3 && sp[-args].type==T_INT)
+      ct=colortable_quant(THIS,min(256,max(2,sp[-args].u.integer)));
+
+   if (args>=3+!!ct)
+   {
+      getrgb(THIS,!!ct,args,"image->togif() (transparency)");
+      transparent=&(THIS->rgb);
+   }
+
+   pop_n_elems(args);
+   if (!THIS->img) { error("no image\n");  return; }
+
+   if (!ct) ct=colortable_quant(THIS,256);
+   push_string( image_encode_gif( THIS,ct, transparent, 0) );
+   colortable_free(ct);
+}
+
+
+void image_togif_fs(INT32 args)
+{
+   rgb_group *transparent=NULL;
+   struct colortable *ct=NULL;
+
+   if (args>0 && sp[-args].type==T_ARRAY)
+      ct=colortable_from_array(sp[-args].u.array,"image->togif_fs()\n");
+   else if (args>0 && args!=3 && sp[-args].type==T_INT)
+      ct=colortable_quant(THIS,min(256,max(2,sp[-args].u.integer)));
+
+   if (args>=3+!!ct)
+   {
+      getrgb(THIS,!!ct,args,"image->togif() (transparency)");
+      transparent=&(THIS->rgb);
+   }
+
+   pop_n_elems(args);
+   if (!THIS->img) { error("no image\n");  return; }
+
+   if (!ct)
+      ct=colortable_quant(THIS,256);
+   push_string( image_encode_gif( THIS,ct, transparent, 1) );
+   colortable_free(ct);
+}
+
+void image_gif_begin(INT32 args)
+{
+   dynamic_buffer buf;
+   long i;
+   int colors,bpp;
+   struct colortable *ct=NULL;
+
+   if (args)
+      if (sp[-args].type==T_INT && sp[-args].u.integer!=0)
+	 ct=colortable_quant(THIS,max(256,min(2,sp[-args].u.integer)));
+      else if (sp[-args].type==T_ARRAY)
+	 ct=colortable_from_array(sp[-args].u.array,"image->gif_begin()\n");
+
+   pop_n_elems(args);
+
+   buf.s.str=NULL;
+   initialize_buf(&buf);
+
+   if (ct)
+   {
+      colors=4; bpp=2;
+      while (colors<ct->numcol) { colors<<=1; bpp++; }
+   }
+   else 
+   {
+      colors=256; bpp=8;
+   }
+
+   low_my_binary_strcat("GIF89a",6,&buf);
+   buf_word((unsigned short)THIS->xsize,&buf);
+   buf_word((unsigned short)THIS->ysize,&buf);
+   low_my_putchar( (char)((0x80*!!ct) | 0x70 | (bpp-1) ), &buf);
+   /* | global colormap | 3 bits color res | sort | 3 bits bpp */
+   /* color res is'nt cared of */
+
+   low_my_putchar( 0, &buf ); /* background color */
+   low_my_putchar( 0, &buf ); /* just zero */
+
+   if (!!ct)
+   {
+      for (i=0; i<ct->numcol; i++)
+      {
+	 low_my_putchar(ct->clut[i].r,&buf);
+	 low_my_putchar(ct->clut[i].g,&buf);
+	 low_my_putchar(ct->clut[i].b,&buf);
+      }
+
+      for (; i<colors; i++)
+      {
+	 low_my_putchar(0,&buf);
+	 low_my_putchar(0,&buf);
+	 low_my_putchar(0,&buf);
+      }
+   }
+   push_string(low_free_buf(&buf));
+}
+
+void image_gif_end(INT32 args)
+{
+   pop_n_elems(args);
+   push_string(make_shared_binary_string(";",1));
+}
+
+void image_gif_netscape_loop(INT32 args)
+{
+   unsigned short loops=0;
+   char buf[30];
+   if (args)
+      if (sp[-args].type!=T_INT) 
+	 error("Illegal argument to image->gif_netscape_loop()\n");
+      else
+	 loops=sp[-args].u.integer;
+   else
+      loops=65535;
+   pop_n_elems(args);
+
+   sprintf(buf,"%c%c%cNETSCAPE2.0%c%c%c%c%c",
+	   33,255,11,3,1,loops&255,(loops>>8)&255,0);
+
+   push_string(make_shared_binary_string(buf,19));
+}
+
+static void img_gif_add(INT32 args,int fs,int lm)
+{
+   INT32 x=0,y=0,i;
+   struct lzw lzw;
+   rgb_group *rgb;
+   struct colortable *ct=NULL;
+   dynamic_buffer buf;
+   int colors,bpp;
+
+CHRONO("gif add init");
+
+   buf.s.str=NULL;
+   initialize_buf(&buf);
+
+   if (args==0) x=y=0;
+   else if (sp[-args].type!=T_INT
+	    || sp[1-args].type!=T_INT)
+      error("Illegal argument(s) to image->gif_add()\n");
+   else 
+   {
+      x=sp[-args].u.integer;
+      y=sp[1-args].u.integer;
+   }
+
+
+   if (args>2 && sp[2-args].type==T_ARRAY)
+      ct=colortable_from_array(sp[2-args].u.array,"image->gif_add()\n");
+   else if (args>3 && sp[2-args].type==T_INT)
+      ct=colortable_quant(THIS,max(256,min(2,sp[2-args].u.integer)));
+
+   if (args>2+!!ct)
+   {
+      unsigned short delay=0;
+      if (sp[2+!!ct-args].type==T_INT) 
+	 delay=sp[2+!!ct-args].u.integer;
+      else if (sp[2+!!ct-args].type==T_FLOAT) 
+	 delay=(unsigned short)(sp[2+!!ct-args].u.float_number*100);
+      else 
+	 error("Illegal argument %d to image->gif_add()\n",3+!!ct);
+
+      low_my_putchar( '!', &buf ); /* extension block */
+      low_my_putchar( 0xf9, &buf ); /* graphics control */
+      low_my_putchar( 4, &buf ); /* block size */
+      low_my_putchar( 0, &buf ); /* disposal, transparency, blabla */
+      buf_word( delay, &buf ); /* delay in centiseconds */
+      low_my_putchar( 0, &buf ); /* (transparency index) */
+      low_my_putchar( 0, &buf ); /* terminate block */
+   }
+
+   ct=colortable_quant(THIS,256);
+
+   colors=4; bpp=2;
+   while (colors<ct->numcol) { colors<<=1; bpp++; }
+
+      
+
+
+   low_my_putchar( ',', &buf ); /* image separator */
+
+   buf_word(x,&buf); /* leftofs */
+   buf_word(y,&buf); /* topofs */
+   buf_word(THIS->xsize,&buf); /* width */
+   buf_word(THIS->ysize,&buf); /* height */
+
+   low_my_putchar((0x80*lm)|(bpp-1), &buf); 
+      /* not interlaced (interlaced == 0x40) */
+      /* local colormap ( == 0x80) */
+      /* 8 bpp in map ( == 0x07)   */
+
+   if (lm)
+   {
+      for (i=0; i<ct->numcol; i++)
+      {
+	 low_my_putchar(ct->clut[i].r,&buf);
+	 low_my_putchar(ct->clut[i].g,&buf);
+	 low_my_putchar(ct->clut[i].b,&buf);
+      }
+      for (; i<colors; i++)
+      {
+	 low_my_putchar(0,&buf);
+	 low_my_putchar(0,&buf);
+	 low_my_putchar(0,&buf);
+      }
+   }
+
+   low_my_putchar( bpp, &buf ); 
+   
+   i=THIS->xsize*THIS->ysize;
+   rgb=THIS->img;
+
+CHRONO("begin pack");
+
+   THREADS_ALLOW();
+   lzw_init(&lzw,bpp);
+   if (!fs)
+      while (i--) lzw_add(&lzw,colortable_rgb(ct,*(rgb++)));
+   else
+   {
+      rgbl_group *errb;
+      rgb_group corgb;
+      int w,*cres,j;
+      errb=(rgbl_group*)xalloc(sizeof(rgbl_group)*THIS->xsize);
+      cres=(int*)xalloc(sizeof(int)*THIS->xsize);
+      for (i=0; i<THIS->xsize; i++)
+	errb[i].r=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+	errb[i].g=(rand()%(FS_SCALE*2+1))-FS_SCALE,
+	errb[i].b=(rand()%(FS_SCALE*2+1))-FS_SCALE;
+
+      w=0;
+      i=THIS->ysize;
+      while (i--)
+      {
+	 image_floyd_steinberg(rgb,THIS->xsize,errb,w=!w,cres,ct);
+	 for (j=0; j<THIS->xsize; j++)
+	    lzw_add(&lzw,cres[j]);
+	 rgb+=THIS->xsize;
+      }
+
+      free(errb);
+      free(cres);
+   }
+
+   lzw_write_last(&lzw);
+
+CHRONO("end pack");
+
+   for (i=0; i<(int)lzw.outpos; i+=254)
+   {
+      int wr;
+      if (i+254>(int)lzw.outpos) wr=lzw.outpos-i;
+      else wr=254;
+      low_my_putchar( (unsigned char)wr, &buf ); /* bytes in chunk */
+      low_my_binary_strcat( (char *) lzw.out+i, wr, &buf );
+   }
+   low_my_putchar( 0, &buf ); /* terminate stream */
+
+   lzw_quit(&lzw);
+
+   colortable_free(ct);
+   THREADS_DISALLOW();
+
+CHRONO("done");
+
+   pop_n_elems(args);
+   push_string(low_free_buf(&buf));
+}
+
+void image_gif_add(INT32 args)
+{
+   img_gif_add(args,0,1);
+}
+
+void image_gif_add_fs(INT32 args)
+{
+   img_gif_add(args,1,1);
+}
+
+void image_gif_add_nomap(INT32 args)
+{
+   img_gif_add(args,0,0);
+}
+
+void image_gif_add_fs_nomap(INT32 args)
+{
+   img_gif_add(args,1,0);
+}
+
diff --git a/src/modules/Mysql/.cvsignore b/src/modules/Mysql/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..323c193f0fa8587cc986797a6352e9c54067ff58
--- /dev/null
+++ b/src/modules/Mysql/.cvsignore
@@ -0,0 +1,9 @@
+.pure
+Makefile
+config.h.in
+config.log
+config.status
+configure
+dependencies
+linker_options
+stamp-h
diff --git a/src/modules/Mysql/.gitignore b/src/modules/Mysql/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5a77561f9be70aacc6af6ea6fa9c40ea15a85a9b
--- /dev/null
+++ b/src/modules/Mysql/.gitignore
@@ -0,0 +1,9 @@
+/.pure
+/Makefile
+/config.h.in
+/config.log
+/config.status
+/configure
+/dependencies
+/linker_options
+/stamp-h
diff --git a/src/modules/Mysql/Makefile.in b/src/modules/Mysql/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..f8992d2d43f64ce07fa3a9d7edcec98d13aec3c3
--- /dev/null
+++ b/src/modules/Mysql/Makefile.in
@@ -0,0 +1,12 @@
+#
+# $Id: Makefile.in,v 1.1 1997/02/11 08:36:33 hubbe Exp $
+#
+
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+MODULE_CPPFLAGS=@DEFS@ @CPPFLAGS@
+OBJS=mysql.o result.o
+MODULE_LDFLAGS=@LDFLAGS@ @MYSQL_LIBS@
+
+@dynamic_module_makefile@
+@dependencies@
\ No newline at end of file
diff --git a/src/modules/Mysql/acconfig.h b/src/modules/Mysql/acconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ca0acfc0291de0d56464fc0ff1111969f3c7f55
--- /dev/null
+++ b/src/modules/Mysql/acconfig.h
@@ -0,0 +1,18 @@
+/*
+ * $Id: acconfig.h,v 1.1 1997/02/11 08:36:34 hubbe Exp $
+ *
+ * Config-file for the Pike mySQL-module.
+ *
+ * Henrik Grubbstr�m 1997-01-30
+ */
+
+#ifndef PIKE_MYSQL_CONFIG_H
+#define PIKE_MYSQL_CONFIG_H
+
+@TOP@
+@BOTTOM@
+
+/* Define if you have mySQL */
+#undef HAVE_MYSQL
+
+#endif /* PIKE_MYSQL_CONFIG_H */
diff --git a/src/modules/Mysql/configure.in b/src/modules/Mysql/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..0231e41d1dbee73a10b31acccb3ce3f297ea1f3c
--- /dev/null
+++ b/src/modules/Mysql/configure.in
@@ -0,0 +1,192 @@
+#
+# $Id: configure.in,v 1.1 1997/02/11 08:36:34 hubbe Exp $
+#
+# Configure script for the mysql-module
+#
+# Henrik Grubbstr�m
+#
+
+#
+# NOTE: 
+#   Prior to 3.20.0          		After 3.20.0
+#   --------------------------------------------------------------
+#   /usr/local/mysql/mach-lib-threads	/usr/local/lib/mysql
+#   /usr/local/mysql/include		/usr/local/include/mysql
+#   libmysql.a				libmysqllib.a
+#   libstrings.a			libmystrings.a
+#
+
+
+AC_INIT(mysql.c)
+AC_CONFIG_HEADER(config.h)
+
+sinclude(../module_configure.in)
+
+OLD_LIBS=$LIBS
+OLD_LDFLAGS=$LDFLAGS
+OLD_CPPFLAGS=$CPPFLAGS
+MYSQL_LIBS=""
+
+AC_ARG_WITH(mysql,  [  --without-mysql       no support for the Mysql database],[],[with_mysql=yes])
+
+if test x$with_mysql = xyes; then
+
+  AC_MSG_CHECKING(Checking for Mysql lib-directory)
+
+  AC_CACHE_VAL(pike_cv_mysql_lib_dir, [
+    for pike_cv_mysql_lib_dir in /usr/local/lib/mysql /usr/local/mysql/lib/mysql /usr/gnu/lib/mysql /usr/lib/mysql /lib/mysql /usr/local/mysql/lib /usr/local/mysql/mach-lib-thread no; do
+      if test -d $pike_cv_mysql_lib_dir/.; then
+        break
+      else
+        :
+      fi
+    done
+  ])
+
+  AC_MSG_RESULT($pike_cv_mysql_lib_dir)
+
+  if test x$pike_cv_mysql_lib_dir = xno; then :; else
+    echo Adding $pike_cv_mysql_lib_dir to the library search path.
+    LDFLAGS="-L$pike_cv_mysql_lib_dir ${LDFLAGS}"
+  fi
+
+  AC_MSG_CHECKING(Checking for Mysql include-directory)
+
+  AC_CACHE_VAL(pike_cv_mysql_include_dir, [
+    for pike_cv_mysql_include_dir in /usr/local/include/mysql /usr/local/mysql/include/mysql /usr/gnu/include/mysql /usr/include/mysql /include/mysql /usr/local/mysql/include no; do
+      if test -d $pike_cv_mysql_include_dir/.; then
+        break
+      else
+	:
+      fi
+    done
+  ])
+
+  AC_MSG_RESULT($pike_cv_mysql_include_dir)
+
+  if test x$pike_cv_mysql_include_dir = xno; then :; else
+    echo Adding $pike_cv_mysql_include_dir to the include search path.
+    CPPFLAGS="-I$pike_cv_mysql_include_dir ${CPPFLAGS}"
+  fi
+
+  # Header file
+
+  AC_CHECK_HEADERS(mysql.h mysql/mysql.h)
+
+  # Mysql libs
+
+  pike_cv_mysql="no"
+
+  AC_CHECK_LIB(mystrings, bchange, [
+    LIBS="-lmystrings $LIBS"
+    MYSQL_LIBS="-lmystrings ${MYSQL_LIBS}"
+    pike_cv_mysql="post3.20"
+  ], [
+    AC_CHECK_LIB(strings, bchange, [
+      LIBS="-lstrings $LIBS"
+      MYSQL_LIBS="-lstrings ${MYSQL_LIBS}"
+      pike_cv_mysql="pre3.20"
+    ], [])
+  ])
+
+  AC_MSG_CHECKING(Mysql version)
+
+  AC_MSG_RESULT($pike_cv_mysql)
+
+  if test x$pike_cv_mysql = xno; then
+    # Restore variables, so we don't link with unnessesary libs
+
+    LIBS=$OLD_LIBS
+    CPPFLAGS=$OLD_CPPFLAGS
+    LDFLAGS=$OLD_LDFLAGS
+    MYSQL_LIBS=""
+  else
+
+    # System libs which might be needed
+
+    if echo $LIBS|grep -- -lsocket >&5 2>&5; then
+      :
+    else
+      AC_CHECK_LIB(socket, socket, [
+        LIBS="-lsocket $LIBS"
+        MYSQL_LIBS="-lsocket ${MYSQL_LIBS}"
+      ], [])
+    fi
+    if echo $LIBS|grep -- -lnsl >&5 2>&5; then
+      :
+    else
+      AC_CHECK_LIB(nsl, gethostbyname, [
+        LIBS="-lnsl $LIBS"
+        MYSQL_LIBS="-lnsl ${MYSQL_LIBS}"
+      ], [])
+    fi
+    if echo $LIBS|grep -- -lm >&5 2>&5; then
+      :
+    else
+      AC_CHECK_LIB(m, floor, [
+        LIBS="-lm $LIBS"
+        MYSQL_LIBS="-lm ${MYSQL_LIBS}"
+      ], [])
+    fi
+
+    # Pthreads is still needed in 3.20.0.
+    AC_CHECK_FUNC(pthread_self, [], [
+      AC_CHECK_LIB(pthread, pthread_self, [
+        LIBS="-lpthread $LIBS"
+	echo Warning added -lpthread to \$LIBS\!
+      ], [
+        AC_CHECK_LIB(pthreads, pthread_self, [
+	  LIBS="-lpthreads $LIBS"
+	  echo Warning added -lpthreads to \$LIBS\!
+	], [])
+      ])
+    ])
+
+    AC_CHECK_LIB(dbug, _db_doprnt_, [
+      LIBS="-ldbug $LIBS"
+      MYSQL_LIBS="-ldbug ${MYSQL_LIBS}"
+    ], [])
+
+    AC_CHECK_LIB(mysys, my_init, [
+      LIBS="-lmysys $LIBS"
+      MYSQL_LIBS="-lmysys ${MYSQL_LIBS}"
+    ], [])
+
+    # Try a couple of mysqlclient libs
+    # in order of age, newest first.
+
+    AC_CHECK_LIB(mysqlclient, mysql_connect, [
+      LIBS="-lmysqlclient $LIBS"
+      MYSQL_LIBS="-lmysqlclient ${MYSQL_LIBS}"
+    ], [ 
+      AC_CHECK_LIB(mysqllib, mysql_connect, [
+	LIBS="-lmysqllib $LIBS"
+	MYSQL_LIBS="-lmysqllib ${MYSQL_LIBS}"
+      ], [
+	AC_CHECK_LIB(mysql, mysql_connect, [
+	  LIBS="-lmysql $LIBS"
+	  MYSQL_LIBS="-lmysql ${MYSQL_LIBS}"
+	], [ pike_cv_mysql="no" ])
+      ])
+    ])
+
+    if test x$pike_cv_mysql = xno; then
+      # Restore variables, so we don't link with unnessesary libs
+
+      LIBS=$OLD_LIBS
+      CPPFLAGS=$OLD_CPPFLAGS
+      LDFLAGS=$OLD_LDFLAGS
+      MYSQL_LIBS=""
+    else
+      AC_DEFINE(HAVE_MYSQL)
+
+      AC_CHECK_FUNCS(mysql_real_query mysql_fetch_lengths)
+    fi
+  fi
+else
+  :
+fi
+
+AC_SUBST(MYSQL_LIBS)
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
diff --git a/src/modules/Mysql/doc/mysql b/src/modules/Mysql/doc/mysql
new file mode 100644
index 0000000000000000000000000000000000000000..8e11cefe880f1606993e05bb220d75cabed3e657
--- /dev/null
+++ b/src/modules/Mysql/doc/mysql
@@ -0,0 +1,378 @@
+NAME
+	/precompiled/sql/mysql - Interface to the Mysql database (ALPHA)
+
+DESCRIPTION
+	/precompiled/sql/mysql is a pre-compiled Pike program. It enables
+	access to the Mysql database from within Pike. /precompiled/sql/mysql
+	is a part of the mysql module.
+
+	Mysql is available from	http://www.tcx.se/ .
+
+KEYWORDS
+	sql, database
+
+
+============================================================================
+NAME
+	create - connect to the Mysql database
+
+SYNTAX
+	#include <mysql.h>
+
+	object(Mysql) Mysql();
+	or
+	object(Mysql) Mysql(string hostname);
+	or
+	object(Mysql) Mysql(string hostname, string database);
+	or
+	object(Mysql) Mysql(string hostname, string database, string user);
+	or
+	object(Mysql) Mysql(string hostname, string database, string user,
+	                    string password);
+
+DESCRIPTION
+	To access the Mysql database, you must first connect to it. This is
+	done with the function Mysql().
+
+	If you give no argument, or give "" as hostname it will connect with
+	a UNIX-domain socket, which is a big performance gain.
+
+
+============================================================================
+NAME
+	affected_rows - return the number of affected rows in the table
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql->affected_rows();
+
+DESCRIPTION
+	Returns the number of affected rows.
+
+
+============================================================================
+NAME
+	insert_id - return the insert id
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql->insert_id();
+
+DESCRIPTION
+	Returns the insert id.
+
+
+============================================================================
+NAME
+	error - return the last error in Mysql
+
+SYNTAX
+	#include <mysql.h>
+
+	string mysql->error();
+
+DESCRIPTION
+	When a Mysql-method fails you can get a description of why with this
+	function.
+
+
+============================================================================
+NAME
+	select_db - select database
+
+SYNTAX
+	#include <mysql.h>
+
+	void select_db(string database);
+
+DESCRIPTION
+	The Mysql-server can hold several databases. You select which one
+	you want to access with this function.
+
+SEE ALSO
+	mysql->create, mysql->create_db, mysql->drop_db
+
+
+============================================================================
+NAME
+	query - make an SQL query
+
+SYNTAX
+	#include <mysql.h>
+
+	int|object(Mysql_result) mysql->query(string q);
+
+DESCRIPTION
+	This function sends an SQL query to the Mysql-server. The result
+	of the query is returned as a /precompiled/sql/mysql_result object.
+	Returns 0 if the query didn't return any result (e.g. INSERT or
+	similar).
+
+SEE ALSO
+	/precompiled/sql/mysql_result
+
+
+============================================================================
+NAME
+	create_db - create a new database
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql->create_db(string database);
+
+DESCRIPTION
+	This function creates a new database in the Mysql-server.
+
+SEE ALSO
+	mysql->select_db, mysql->drop_db
+
+
+============================================================================
+NAME
+	drop_db - drop a database
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql->drop_db(string database);
+
+DESCRIPTION
+	This function drops a database from a Mysql-server.
+
+SEE ALSO
+	mysql->create_db, mysql->select_db
+
+
+============================================================================
+NAME
+	shutdown - shutdown the Mysql-server
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql->shutdown();
+
+DESCRIPTION
+	This function shuts down a running Mysql-server.
+
+SEE ALSO
+	mysql->reload
+
+
+============================================================================
+NAME
+	reload - reload the tables
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql->reload();
+
+DESCRIPTION
+	This function causes the Mysql-server to reload its tables.
+
+SEE ALSO
+	mysql->shutdown
+
+
+============================================================================
+NAME
+	statistics - some Mysql-server statistics
+
+SYNTAX
+	#include <mysql.h>
+
+	string mysql->statistics();
+
+DESCRIPTION
+	This function returns some server statistics.
+
+EXAMPLE
+
+	#include <mysql.h>
+
+	int main()
+	{
+	  write(Mysql()->statistics());
+	  return(0);
+	}
+
+SEE ALSO
+	mysql->server_info, mysql->host_info, mysql->protocol_info
+
+
+============================================================================
+NAME
+	server_info - give the version number of the Mysql-server
+
+SYNTAX
+	#include <mysql.h>
+
+	string mysql->server_info();
+
+DESCRIPTION
+	This function returns the version number of the Mysql-server.
+
+SEE ALSO
+	mysql->statistics, mysql->host_info, mysql->protocol_info
+
+
+============================================================================
+NAME
+	host_info - give information about the Mysql-server connection
+
+SYNTAX
+	#include <mysql.h>
+
+	string mysql->host_info();
+
+DESCRIPTION
+	This function returns a string describing the connection to
+	the Mysql-server.
+
+SEE ALSO
+	mysql->statistics, mysql->server_info, mysql->protocol_info
+
+
+============================================================================
+NAME
+	protocol_info - give the Mysql protocol version
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql->protocol_info();
+
+DESCRIPTION
+	This function returns the version number of the protocol the
+	Mysql-server uses.
+
+SEE ALSO
+	mysql->statistics, mysql->server_info, mysql->host_info
+
+
+============================================================================
+NAME
+	list_dbs - list databases
+
+SYNTAX
+	#include <mysql.h>
+
+	object(Mysql_result) mysql->list_dbs();
+	or
+	object(Mysql_result) mysql->list_dbs(string wild);
+
+DESCRIPTION
+	Returns a table containing the names of all databases in the
+	Mysql-server. If an argument is specified, only those matching
+	wild are returned.
+
+SEE ALSO
+	mysql->list_tables, mysql->list_fields, mysql->list_processes,
+	/precompiled/sql/mysql_result
+
+
+============================================================================
+NAME
+	list_tables - list tables in the current database
+
+SYNTAX
+	#include <mysql.h>
+
+	object(Mysql_result) mysql->list_tables();
+	or
+	object(Mysql_result) mysql->list_tables(string wild);
+
+DESCRIPTION
+	Returns a table containing the names of all tables in the current
+	database. If an argument is given, only those matching wild are
+	returned.
+
+SEE ALSO
+	mysql->list_dbs, mysql->list_fields, mysql->list_processes,
+	/precompiled/sql/mysql_result
+
+
+============================================================================
+NAME
+	list_fields - list all fields
+
+SYNTAX
+	#include <mysql.h>
+
+	array(int|mapping(string:mixed)) mysql->list_fields(string table);
+	or
+	array(int|mapping(string:mixed)) mysql->list_fields(string table,
+	                                                    string wild);
+
+DESCRIPTION
+	Returns an array of mappings with information about the fields in the
+	specified table.
+
+	The mappings contain the following entries:
+
+	 "name":	string	The name of the field.
+	 "table":	string	The name of the table.
+	 "default":	string	The default value for the field.
+	 "type":	string	The type of the field.
+	 "length":	int	The length of the field.
+	 "max_length":	int	The length of the longest element in this field.
+	 "flags":	multiset(string)	Some flags.
+	 "decimals":	int	The number of decimalplaces.
+
+	The type of the field can be any of:
+	"decimal", "char", "short", "long", "float", "double", "null",
+	"time", "longlong", "int24", "tiny blob", "medium blob",
+	"long blob", "var string", "string" or "unknown".
+
+	The flags multiset can contain any of
+	 "primary_key":	This field is part of the primary key for this table.
+	 "not_null":	This field may not be NULL.
+	 "blob":	This field is a blob field.
+
+NOTA BENE
+	Michael Widenius recomends usage of the following query instead:
+	 show fields in 'table' like "wild"
+
+SEE ALSO
+	mysql->list_dbs, mysql->list_tables, mysql->list_processes,
+	mysql_result->fetch_fields
+
+
+============================================================================
+NAME
+	list_processes - list all processes in the Mysql-server
+
+SYNTAX
+	#include <mysql.h>
+
+	object(Mysql_result) mysql->list_processes();
+
+DESCRIPTION
+	Returns a table containing the names of all processes in the
+	Mysql-server.
+
+SEE ALSO
+	mysql->list_dbs, mysql->list_tables, mysql->list_fields,
+	/precompiled/sql/mysql_result
+
+
+============================================================================
+NAME
+	binary_data - inform if this version of mysql supports binary data
+
+SYNTAX
+	int mysql->binary_data();
+
+DESCRIPTION
+	This function returns non-zero if binary data can be reliably stored
+	and retreived with this version of the mysql-module.
+
+	Usually, there is no problem storing binary data in mysql-tables,
+	but data containing '\0' (NUL) couldn't be fetched with old
+	versions (prior to 3.20.5) of the mysql-library.
+
+
diff --git a/src/modules/Mysql/doc/mysql_result b/src/modules/Mysql/doc/mysql_result
new file mode 100644
index 0000000000000000000000000000000000000000..f62e5baa142f76ee1d89d7a964bc3fd227afea74
--- /dev/null
+++ b/src/modules/Mysql/doc/mysql_result
@@ -0,0 +1,204 @@
+NAME
+	/precompiled/sql/mysql_result - result of an Mysql query (ALPHA)
+
+DESCRIPTION
+	/precompiled/sql/mysql_result is a pre-compiled Pike program. It
+	contains the result of a Mysql-query. /precompiled/sql/mysql_result
+	is a part of the mysql module.
+
+	Mysql is available from http://www.tcx.se/ .
+
+KEYWORDS
+	sql, database
+
+SEE ALSO
+	/precompiled/sql/mysql
+
+
+============================================================================
+NAME
+	create - make a new mysql_result object
+
+SYNTAX
+	#include <mysql.h>
+
+	object(Mysql_result) mysql->query(string q);
+	or
+	object(Mysql_result) mysql->list_dbs();
+	or
+	object(Mysql_result) mysql->list_dbs(string wild);
+	or
+	object(Mysql_result) mysql->list_tables();
+	or
+	object(Mysql_result) mysql->list_tables(string wild);
+	or
+	object(Mysql_result) mysql->list_processes();
+
+DESCRIPTION
+	Creates a Mysql result table.
+
+SEE ALSO
+	mysql->query, mysql->list_dbs, mysql->list_tables,
+	mysql->list_processes, /precompiled/sql/mysql
+
+
+============================================================================
+NAME
+	num_rows - number of rows in the result
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql_result->num_rows();
+
+DESCRIPTION
+	Returns the number of rows in the result.
+
+SEE ALSO
+	mysql_result->num_fields
+
+
+============================================================================
+NAME
+	num_fields - number of fields in the result
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql_result->num_fields();
+
+DESCRIPTION
+	Returns the number of fields in the result.
+
+SEE ALSO
+	mysql_result->num_rows
+
+
+============================================================================
+NAME
+	field_seek - skip to specified field (OPTIONAL)
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql_result->field_seek(int field_no);
+
+DESCRIPTION
+	Places the field cursor at the specified position. This affects
+	which field mysql_result->fetch_field() will return next.
+
+	Fields are numbered starting with 0.
+
+NOTA BENE
+	This function exists only if SUPPORT_FIELD_SEEK was defined when
+	compiling the mysql-module.
+
+SEE ALSO
+	mysql_result->fetch_field, mysql_result->fetch_fields
+
+
+============================================================================
+NAME
+	eof - at end of result table
+
+SYNTAX
+	#include <mysql.h>
+
+	int mysql_result->eof();
+
+DESCRIPTION
+	Returns non-zero when all rows have been read.
+
+SEE ALSO
+	mysql_result->fetch_row
+
+
+============================================================================
+NAME
+	fetch_field - return specification of current field (OPTIONAL)
+
+SYNTAX
+	#include <mysql.h>
+
+	int|mapping(string:mixed) mysql_result->fetch_field();
+
+DESCRIPTION
+	Returns a mapping with information about the current field, and
+	advances the field cursor one step. Returns 0 if there are no more
+	fields.
+
+	The mapping contains the same entries as those returned by
+	mysql->list_fields(), except that the "default" entry is missing.
+
+NOTA BENE
+	This function exists only if SUPPORT_FIELD_SEEK was defined when
+	compiling the mysql-module.
+
+SEE ALSO
+	mysql_result->fetch_fields, mysql_result->field_seek,
+	sql->list_fields
+
+
+============================================================================
+NAME
+	fetch_fields - return specification of all remaining fields
+
+SYNTAX
+	#include <mysql.h>
+
+	array(int|mapping(string:mixed)) mysql_result->fetch_fields();
+
+DESCRIPTION
+	Returns an array with one mapping for every remaining field in the
+	result table.
+
+	It returns data similar to mysql->list_fields(), except for that
+	the "default" entry is missing.
+
+NOTA BENE
+	Resets the field cursor to 0.
+
+	This function exists even if SUPPORT_FIELD_SEEK wasn't defined when
+	the Mysql-module was compiled.
+
+SEE ALSO
+	mysql_result->fetch_field, mysql_result->field_seek,
+	mysql->list_fields
+
+
+============================================================================
+NAME
+	seek - skip ahead a number of rows
+
+SYNTAX
+	#include <mysql.h>
+
+	void mysql_result->seek(int r);
+
+DESCRIPTION
+	Skips ahead the specified number of rows.
+
+BUGS
+	Can only seek forward (limitation in Mysql).
+
+SEE ALSO
+	mysql_result->fetch_row
+
+
+============================================================================
+NAME
+	fetch_row - fetch the next row from the result
+
+SYNTAX
+	#include <mysql.h>
+
+	int|array(string|int) mysql_result->fetch_row();
+
+DESCRIPTION
+	Returns an array with the contents of the next row in the result.
+	Advances the row cursor to the next row. Returns 0 at end of table.
+
+SEE ALSO
+	mysql_result->seek
+
+
diff --git a/src/modules/Mysql/mysql.c b/src/modules/Mysql/mysql.c
new file mode 100644
index 0000000000000000000000000000000000000000..87f7968202cdb6813e158564660e880fdd19da6d
--- /dev/null
+++ b/src/modules/Mysql/mysql.c
@@ -0,0 +1,706 @@
+/*
+ * $Id: mysql.c,v 1.1 1997/02/11 08:36:34 hubbe Exp $
+ *
+ * SQL database functionality for Pike
+ *
+ * Henrik Grubbstr�m 1996-12-21
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#ifdef HAVE_MYSQL
+
+/*
+ * Includes
+ */
+
+/* From the mysql-dist */
+/* Workaround for versions prior to 3.20.0 not beeing protected for
+ * multiple inclusion.
+ */
+#ifndef _mysql_h
+#ifdef HAVE_MYSQL_H
+#include <mysql.h>
+#else
+#ifdef HAVE_MYSQL_MYSQL_H
+#include <mysql/mysql.h>
+#else
+#error Need mysql.h header-file
+#endif /* HAVE_MYSQL_MYSQL_H */
+#endif /* HAVE_MYSQL_H */
+#ifndef _mysql_h
+#define _mysql_h
+#endif
+#endif
+
+/* dynamic_buffer.h contains a conflicting typedef for string
+ * we don't use any dynamic buffers, so we have this work-around
+ */
+#define DYNAMIC_BUFFER_H
+typedef struct dynamic_buffer_s dynamic_buffer;
+
+#endif /* HAVE_MYSQL */
+
+/* From the Pike-dist */
+#include <global.h>
+#include <svalue.h>
+#include <object.h>
+#include <stralloc.h>
+#include <interpret.h>
+#include <port.h>
+#include <error.h>
+#include <las.h>
+#include <threads.h>
+
+/* System includes */
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+
+#ifdef HAVE_MYSQL
+
+/* Local includes */
+#include "precompiled_mysql.h"
+
+/*
+ * Globals
+ */
+
+RCSID("$Id: mysql.c,v 1.1 1997/02/11 08:36:34 hubbe Exp $");
+
+struct program *mysql_program = NULL;
+
+/*
+ * Functions
+ */
+
+/*
+ * State maintenance
+ */
+
+static void init_mysql_struct(struct object *o)
+{
+  MEMSET(PIKE_MYSQL, 0, sizeof(struct precompiled_mysql));
+}
+
+static void exit_mysql_struct(struct object *o)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *last_result = PIKE_MYSQL->last_result;
+
+  PIKE_MYSQL->last_result = NULL;
+  PIKE_MYSQL->socket = NULL;
+
+  THREADS_ALLOW();
+
+  if (last_result) {
+    mysql_free_result(last_result);
+  }
+  if (socket) {
+    mysql_close(socket);
+  }
+
+  THREADS_DISALLOW();
+}
+
+/*
+ * Methods
+ */
+
+/* void create(string|void host, string|void database, string|void user, string|void password) */
+static void f_create(INT32 args)
+{
+  MYSQL *mysql = &PIKE_MYSQL->mysql;
+  MYSQL *socket;
+  char *host = NULL;
+  char *database = NULL;
+  char *user = NULL;
+  char *password = NULL;
+
+  if (args >= 1) {
+    if (sp[-args].type != T_STRING) {
+      error("Bad argument 1 to mysql()\n");
+    }
+    if (sp[-args].u.string->len) {
+      host = sp[-args].u.string->str;
+    }
+
+    if (args >= 2) {
+      if (sp[1-args].type != T_STRING) {
+	error("Bad argument 2 to mysql()\n");
+      }
+      if (sp[1-args].u.string->len) {
+	database = sp[1-args].u.string->str;
+      }
+
+      if (args >= 3) {
+	if (sp[2-args].type != T_STRING) {
+	  error("Bad argument 3 to mysql()\n");
+	}
+	if (sp[2-args].u.string->len) {
+	  user = sp[2-args].u.string->str;
+	}
+
+	if (args >= 4) {
+	  if (sp[3-args].type != T_STRING) {
+	    error("Bad argument 4 to mysql()\n");
+	  }
+	  if (sp[3-args].u.string->len) {
+	    password = sp[3-args].u.string->str;
+	  }
+	}
+      }
+    }
+  }
+
+  THREADS_ALLOW();
+
+  socket = mysql_connect(mysql, host, user, password);
+
+  THREADS_DISALLOW();
+
+  if (!(PIKE_MYSQL->socket = socket)) {
+    error("mysql(): Couldn't connect to SQL-server\n");
+  }
+  if (database) {
+    int tmp;
+
+    THREADS_ALLOW();
+
+    tmp = mysql_select_db(socket, database);
+
+    THREADS_DISALLOW();
+
+    if (tmp < 0) {
+      mysql_close(PIKE_MYSQL->socket);
+      PIKE_MYSQL->socket = NULL;
+      error("mysql(): Couldn't select database \"%s\"\n", database);
+    }
+  }
+
+  pop_n_elems(args);
+}
+
+/* int affected_rows() */
+static void f_affected_rows(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(mysql_affected_rows(PIKE_MYSQL->socket));
+}
+
+/* int insert_id() */
+static void f_insert_id(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(mysql_insert_id(PIKE_MYSQL->socket));
+}
+
+/* int|string error() */
+static void f_error(INT32 args)
+{
+  char *error_msg = mysql_error(PIKE_MYSQL->socket);
+
+  pop_n_elems(args);
+  if (error_msg && *error_msg) {
+    push_text(error_msg);
+  } else {
+    push_int(0);
+  }
+}
+
+/* void select_db(string database) */
+static void f_select_db(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  char *database;
+  int tmp;
+
+  if (!args) {
+    error("Too few arguments to mysql->select_db()\n");
+  }
+  if (sp[-args].type != T_STRING) {
+    error("Bad argument 1 to mysql->select_db()\n");
+  }
+
+  database = sp[-args].u.string->str;
+
+  THREADS_ALLOW();
+
+  tmp = mysql_select_db(socket, database);
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->select_db(): Couldn't select database \"%s\"\n",
+	  sp[-args].u.string->str);
+  }
+
+  pop_n_elems(args);
+}
+
+/* object(mysql_result) big_query(string q) */
+static void f_big_query(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *result;
+  char *query;
+  int qlen;
+  int tmp;
+
+  if (!args) {
+    error("Too few arguments to mysql->big_query()\n");
+  }
+  if (sp[-args].type != T_STRING) {
+    error("Bad argument 1 to mysql->big_query()\n");
+  }
+
+  query = sp[-args].u.string->str;
+  qlen = sp[-args].u.string->len;
+
+  THREADS_ALLOW();
+
+  /* NOTE the temporary tmp is needed since THREADS_ALLOW() opens a scope,
+   * which is closed at THREADS_DISALLOW()
+   */
+
+#ifdef HAVE_MYSQL_REAL_QUERY
+  tmp = mysql_real_query(socket, query, qlen);
+#else
+  tmp = mysql_query(socket, query);
+#endif /* HAVE_MYSQL_REAL_QUERY */
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->big_query(): Query \"%s\" failed\n",
+	  sp[-args].u.string->str);
+  }
+
+  THREADS_ALLOW();
+
+  /* The same thing applies here */
+
+  result = mysql_store_result(socket);
+
+  THREADS_DISALLOW();
+
+  pop_n_elems(args);
+
+  if (!(PIKE_MYSQL->last_result = result)) {
+    if (mysql_num_fields(socket) && mysql_error(socket)[0]) {
+      error("mysql->big_query(): Couldn't create result for query\n");
+    }
+    /* query was INSERT or similar - return 0 */
+
+    push_int(0);
+  } else {
+    /* Return the result-object */
+
+    push_object(fp->current_object);
+    fp->current_object->refs++;
+
+    push_object(clone(mysql_result_program, 1));
+  }
+}
+
+/* void create_db(string database) */
+static void f_create_db(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  char *database;
+  int tmp;
+
+  if (!args) {
+    error("Too few arguments to mysql->create_db()\n");
+  }
+  if (sp[-args].type != T_STRING) {
+    error("Bad argument 1 to mysql->create_db()\n");
+  }
+  if (sp[-args].u.string->len > 127) {
+    error("Database name \"%s\" is too long (max 127 characters)\n",
+	  sp[-args].u.string->str);
+  }
+  database = sp[-args].u.string->str;
+
+  THREADS_ALLOW();
+
+  tmp = mysql_create_db(socket, database);
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->create_db(): Creation of database \"%s\" failed\n",
+	  sp[-args].u.string->str);
+  }
+
+  pop_n_elems(args);
+}
+
+/* void drop_db(string database) */
+static void f_drop_db(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  char *database;
+  int tmp;
+
+  if (!args) {
+    error("Too few arguments to mysql->drop_db()\n");
+  }
+  if (sp[-args].type != T_STRING) {
+    error("Bad argument 1 to mysql->drop_db()\n");
+  }
+  if (sp[-args].u.string->len > 127) {
+    error("Database name \"%s\" is too long (max 127 characters)\n",
+	  sp[-args].u.string->str);
+  }
+  database = sp[-args].u.string->str;
+
+  THREADS_ALLOW();
+
+  tmp = mysql_drop_db(socket, database);
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->drop_db(): Drop of database \"%s\" failed\n",
+	  sp[-args].u.string->str);
+  }
+
+  pop_n_elems(args);
+}
+
+/* void shutdown() */
+static void f_shutdown(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  int tmp;
+
+  THREADS_ALLOW();
+  
+  tmp = mysql_shutdown(socket);
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->shutdown(): Shutdown failed\n");
+  }
+
+  pop_n_elems(args);
+}
+
+/* void reload() */
+static void f_reload(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  int tmp;
+
+  THREADS_ALLOW();
+
+  tmp = mysql_reload(socket);
+
+  THREADS_DISALLOW();
+
+  if (tmp < 0) {
+    error("mysql->reload(): Reload failed\n");
+  }
+
+  pop_n_elems(args);
+}
+
+/* string statistics() */
+static void f_statistics(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  char *stats;
+
+  pop_n_elems(args);
+
+  THREADS_ALLOW();
+
+  stats = mysql_stat(socket);
+
+  THREADS_DISALLOW();
+
+  push_text(stats);
+}
+
+/* string server_info() */
+static void f_server_info(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  char *info;
+
+  pop_n_elems(args);
+
+  push_text("mysql/");
+
+  THREADS_ALLOW();
+
+  info = mysql_get_server_info(socket);
+
+  THREADS_DISALLOW();
+
+  push_text(info);
+  f_add(2);
+}
+
+/* string host_info() */
+static void f_host_info(INT32 args)
+{
+  pop_n_elems(args);
+
+  push_text(mysql_get_host_info(PIKE_MYSQL->socket));
+}
+
+/* int protocol_info() */
+static void f_protocol_info(INT32 args)
+{
+  pop_n_elems(args);
+
+  push_int(mysql_get_proto_info(PIKE_MYSQL->socket));
+}
+
+/* object(mysql_res) list_dbs(void|string wild) */
+static void f_list_dbs(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *result;
+  char *wild = NULL;
+
+  if (args) {
+    if (sp[-args].type != T_STRING) {
+      error("Bad argument 1 to mysql->list_dbs()\n");
+    }
+    if (sp[-args].u.string->len > 80) {
+      error("Wildcard \"%s\" is too long (max 80 characters)\n",
+	    sp[-args].u.string->str);
+    }
+    wild = sp[-args].u.string->str;
+  }
+
+  THREADS_ALLOW();
+
+  result = mysql_list_dbs(socket, wild);
+
+  THREADS_DISALLOW();
+
+  if (!(PIKE_MYSQL->last_result = result)) {
+    error("mysql->list_dbs(): Cannot list databases: %s\n",
+	  mysql_error(PIKE_MYSQL->socket));
+  }
+
+  pop_n_elems(args);
+
+  push_object(fp->current_object);
+  fp->current_object->refs++;
+
+  push_object(clone(mysql_result_program, 1));
+}
+
+/* object(mysql_res) list_tables(void|string wild) */
+static void f_list_tables(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *result;
+  char *wild = NULL;
+
+  if (args) {
+    if (sp[-args].type != T_STRING) {
+      error("Bad argument 1 to mysql->list_tables()\n");
+    }
+    if (sp[-args].u.string->len > 80) {
+      error("Wildcard \"%s\" is too long (max 80 characters)\n",
+	    sp[-args].u.string->str);
+    }
+    wild = sp[-args].u.string->str;
+  }
+
+  THREADS_ALLOW();
+
+  result = mysql_list_tables(socket, wild);
+
+  THREADS_DISALLOW();
+
+  if (!(PIKE_MYSQL->last_result = result)) {
+    error("mysql->list_tables(): Cannot list databases: %s\n",
+	  mysql_error(PIKE_MYSQL->socket));
+  }
+
+  pop_n_elems(args);
+
+  push_object(fp->current_object);
+  fp->current_object->refs++;
+
+  push_object(clone(mysql_result_program, 1));
+}
+
+/* array(int|mapping(string:mixed)) list_fields(string table, void|string wild) */
+static void f_list_fields(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *result;
+  MYSQL_FIELD *field;
+  int i = 0;
+  char *table;
+  char *wild = NULL;
+
+  if (!args) {
+    error("Too few arguments to mysql->list_fields()\n");
+  }
+  
+  if (sp[-args].type != T_STRING) {
+    error("Bad argument 1 to mysql->list_fields()\n");
+  }
+  if (sp[-args].u.string->len > 125) {
+    error("Table name \"%s\" is too long (max 125 characters)\n",
+	  sp[-args].u.string->str);
+  }
+  table = sp[-args].u.string->str;
+  if (args > 1) {
+    if (sp[-args+1].type != T_STRING) {
+      error("Bad argument 2 to mysql->list_fields()\n");
+    }
+    if (sp[-args+1].u.string->len + sp[-args].u.string->len > 125) {
+      error("Wildcard \"%s\" + table name \"%s\" is too long "
+	    "(max 125 characters)\n",
+	    sp[-args+1].u.string->str, sp[-args].u.string->str);
+    }
+    wild = sp[-args+1].u.string->str;
+  }
+
+  THREADS_ALLOW();
+
+  result = mysql_list_fields(socket, table, wild);
+
+  THREADS_DISALLOW();
+
+  if (!result) {
+    error("mysql->list_fields(): Cannot list databases: %s\n",
+	  mysql_error(PIKE_MYSQL->socket));
+  }
+
+  pop_n_elems(args);
+
+  while ((field = mysql_fetch_field(result))) {
+    mysqlmod_parse_field(field, 1);
+    i++;
+  }
+  f_aggregate(i);
+}
+
+/* object(mysql_res) list_processes() */
+static void f_list_processes(INT32 args)
+{
+  MYSQL *socket = PIKE_MYSQL->socket;
+  MYSQL_RES *result;
+
+  pop_n_elems(args);
+
+  THREADS_ALLOW();
+
+  result = mysql_list_processes(socket);
+
+  THREADS_DISALLOW();
+
+  if (!(PIKE_MYSQL->last_result = result)) {
+    error("mysql->list_processes(): Cannot list databases: %s\n",
+	  mysql_error(PIKE_MYSQL->socket));
+  }
+
+  push_object(fp->current_object);
+  fp->current_object->refs++;
+
+  push_object(clone(mysql_result_program, 1));
+}
+
+/*
+ * Support for binary data in tables
+ */
+static void f_binary_data(INT32 args)
+{
+  pop_n_elems(args);
+#ifdef HAVE_MYSQL_FETCH_LENGTHS
+  push_int(1);
+#else
+  push_int(0);
+#endif /* HAVE_MYSQL_FETCH_LENGTHS */
+}
+
+#endif /* HAVE_MYSQL */
+
+/*
+ * Module linkage
+ */
+
+
+void pike_module_init(void)
+{
+#ifdef HAVE_MYSQL
+  /*
+   * start_new_program();
+   *
+   * add_storage();
+   *
+   * add_function();
+   * add_function();
+   * ...
+   *
+   * set_init_callback();
+   * set_exit_callback();
+   *
+   * program = end_c_program();
+   * program->refs++;
+   *
+   */
+ 
+  start_new_program();
+  add_storage(sizeof(struct precompiled_mysql));
+
+  add_function("error", f_error, "function(void:int|string)", OPT_EXTERNAL_DEPEND);
+  add_function("create", f_create, "function(string|void, string|void, string|void, string|void:void)", OPT_SIDE_EFFECT);
+  add_function("affected_rows", f_affected_rows, "function(void:int)", OPT_EXTERNAL_DEPEND);
+  add_function("insert_id", f_insert_id, "function(void:int)", OPT_EXTERNAL_DEPEND);
+  add_function("select_db", f_select_db, "function(string:void)", OPT_SIDE_EFFECT);
+  add_function("big_query", f_big_query, "function(string:int|object)", OPT_EXTERNAL_DEPEND);
+  add_function("create_db", f_create_db, "function(string:void)", OPT_SIDE_EFFECT);
+  add_function("drop_db", f_drop_db, "function(string:void)", OPT_SIDE_EFFECT);
+  add_function("shutdown", f_shutdown, "function(void:void)", OPT_SIDE_EFFECT);
+  add_function("reload", f_reload, "function(void:void)", OPT_SIDE_EFFECT);
+  add_function("statistics", f_statistics, "function(void:string)", OPT_EXTERNAL_DEPEND);
+  add_function("server_info", f_server_info, "function(void:string)", OPT_EXTERNAL_DEPEND);
+  add_function("host_info", f_host_info, "function(void:string)", OPT_EXTERNAL_DEPEND);
+  add_function("protocol_info", f_protocol_info, "function(void:int)", OPT_EXTERNAL_DEPEND);
+  add_function("list_dbs", f_list_dbs, "function(void|string:object)", OPT_EXTERNAL_DEPEND);
+  add_function("list_tables", f_list_tables, "function(void|string:object)", OPT_EXTERNAL_DEPEND);
+  add_function("list_fields", f_list_fields, "function(string, void|string:array(int|mapping(string:mixed)))", OPT_EXTERNAL_DEPEND);
+  add_function("list_processes", f_list_processes, "function(void|string:object)", OPT_EXTERNAL_DEPEND);
+
+  add_function("binary_data", f_binary_data, "function(void:int)", OPT_TRY_OPTIMIZE);
+
+  set_init_callback(init_mysql_struct);
+  set_exit_callback(exit_mysql_struct);
+
+  mysql_program = end_program();
+  add_program_constant("mysql", mysql_program, 0);
+
+  init_mysql_res_programs();
+#endif /* HAVE_MYSQL */
+}
+
+void pike_module_exit(void)
+{
+#ifdef HAVE_MYSQL
+  exit_mysql_res();
+
+  if (mysql_program) {
+    free_program(mysql_program);
+    mysql_program = NULL;
+  }
+#endif /* HAVE_MYSQL */
+}
+
diff --git a/src/modules/Mysql/precompiled_mysql.h b/src/modules/Mysql/precompiled_mysql.h
new file mode 100644
index 0000000000000000000000000000000000000000..e56e403586bcb34b21b31b234768f263ac19c84b
--- /dev/null
+++ b/src/modules/Mysql/precompiled_mysql.h
@@ -0,0 +1,78 @@
+/*
+ * $Id: precompiled_mysql.h,v 1.1 1997/02/11 08:36:35 hubbe Exp $
+ *
+ * SQL database connectivity for Pike
+ *
+ * Henrik Grubbstr�m 1996-12-21
+ */
+
+#ifndef PRECOMPILED_MYSQL_H
+#define PRECOMPILED_MYSQL_H
+
+/*
+ * Includes
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+/* From the mysql-dist */
+/* Workaround for versions prior to 3.20.0 not beeing protected for
+ * multiple inclusion.
+ */
+#ifndef _mysql_h
+#ifdef HAVE_MYSQL_H
+#include <mysql.h>
+#else
+#ifdef HAVE_MYSQL_MYSQL_H
+#include <mysql/mysql.h>
+#else
+#error Need mysql.h header-file
+#endif /* HAVE_MYSQL_MYSQL_H */
+#endif /* HAVE_MYSQL_H */
+#ifndef _mysql_h
+#define _mysql_h
+#endif
+#endif
+
+/* From the Pike-dist */
+
+/*
+ * Structures
+ */
+
+struct precompiled_mysql {
+  MYSQL		mysql, *socket;
+  MYSQL_RES	*last_result;	/* UGLY way to pass arguments to create() */
+};
+
+struct precompiled_mysql_result {
+  MYSQL_RES	*result;
+};
+
+/*
+ * Defines
+ */
+
+#define PIKE_MYSQL	((struct precompiled_mysql *)(fp->current_storage))
+#define PIKE_MYSQL_RES	((struct precompiled_mysql_result *)(fp->current_storage))
+
+/*
+ * Globals
+ */
+
+extern struct program *mysql_program;
+extern struct program *mysql_result_program;
+
+/*
+ * Prototypes
+ */
+
+/* From result.c */
+
+void init_mysql_res_efuns(void);
+void init_mysql_res_programs(void);
+void exit_mysql_res(void);
+
+#endif /* PRECOMPILED_MYSQL_H */
diff --git a/src/modules/Mysql/result.c b/src/modules/Mysql/result.c
new file mode 100644
index 0000000000000000000000000000000000000000..6e9f6d9a9dd93bdd219547a24f2453e84af6fea6
--- /dev/null
+++ b/src/modules/Mysql/result.c
@@ -0,0 +1,449 @@
+/*
+ * $Id: result.c,v 1.1 1997/02/11 08:36:35 hubbe Exp $
+ *
+ * mysql query result
+ *
+ * Henrik Grubbstr�m 1996-12-21
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#ifdef HAVE_MYSQL
+/*
+ * Includes
+ */
+
+/* From the mysql-dist */
+/* Workaround for versions prior to 3.20.0 not beeing protected for
+ * multiple inclusion.
+ */
+#ifndef _mysql_h
+#ifdef HAVE_MYSQL_H
+#include <mysql.h>
+#else
+#ifdef HAVE_MYSQL_MYSQL_H
+#include <mysql/mysql.h>
+#else
+#error Need mysql.h header-file
+#endif /* HAVE_MYSQL_MYSQL_H */
+#endif /* HAVE_MYSQL_H */
+#ifndef _mysql_h
+#define _mysql_h
+#endif
+#endif
+
+/* dynamic_buffer.h contains a conflicting typedef for string
+ * we don't use any dynamic buffers, so we have this work-around
+ */
+#define DYNAMIC_BUFFER_H
+typedef struct dynamic_buffer_s dynamic_buffer;
+
+/* From the Pike-dist */
+#include <global.h>
+#include <svalue.h>
+#include <mapping.h>
+#include <object.h>
+#include <program.h>
+#include <stralloc.h>
+#include <interpret.h>
+#include <error.h>
+#include <builtin_functions.h>
+#include <las.h>
+#include <threads.h>
+
+/* Local includes */
+#include "precompiled_mysql.h"
+
+/* System includes */
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+
+/* Define this to get support for field->default. NOT SUPPORTED */
+#undef SUPPORT_DEFAULT
+
+/* Define this to get field_seek() and fetch_field() */
+/* #define SUPPORT_FIELD_SEEK */
+
+/*
+ * Globals
+ */
+
+RCSID("$Id: result.c,v 1.1 1997/02/11 08:36:35 hubbe Exp $");
+
+struct program *mysql_result_program = NULL;
+
+/*
+ * Functions
+ */
+
+/*
+ * State maintenance
+ */
+
+static void init_res_struct(struct object *o)
+{
+  memset(PIKE_MYSQL_RES, 0, sizeof(struct precompiled_mysql_result));
+}
+
+static void exit_res_struct(struct object *o)
+{
+  if (PIKE_MYSQL_RES->result) {
+    mysql_free_result(PIKE_MYSQL_RES->result);
+    PIKE_MYSQL_RES->result = NULL;
+  }
+}
+
+/*
+ * Help functions
+ */
+
+void mysqlmod_parse_field(MYSQL_FIELD *field, int support_default)
+{
+  if (field) {
+    int nbits = 0;
+    push_text("name"); push_text(field->name);
+    push_text("table"); push_text(field->table);
+    if (support_default) {
+      push_text("default");
+      if (field->def) {
+	push_text(field->def);
+      } else {
+	push_int(0);
+      }
+    }
+    push_text("type");
+    switch(field->type) {
+    case FIELD_TYPE_DECIMAL:
+      push_text("decimal");
+      break;
+    case FIELD_TYPE_CHAR:
+      push_text("char");
+      break;
+    case FIELD_TYPE_SHORT:
+      push_text("short");
+      break;
+    case FIELD_TYPE_LONG:
+      push_text("long");
+      break;
+    case FIELD_TYPE_FLOAT:
+      push_text("float");
+      break;
+    case FIELD_TYPE_DOUBLE:
+      push_text("double");
+      break;
+    case FIELD_TYPE_NULL:
+      push_text("null");
+      break;
+    case FIELD_TYPE_TIME:
+      push_text("time");
+      break;
+    case FIELD_TYPE_LONGLONG:
+      push_text("longlong");
+      break;
+    case FIELD_TYPE_INT24:
+      push_text("int24");
+      break;
+    case FIELD_TYPE_TINY_BLOB:
+      push_text("tiny blob");
+      break;
+    case FIELD_TYPE_MEDIUM_BLOB:
+      push_text("medium blob");
+      break;
+    case FIELD_TYPE_LONG_BLOB:
+      push_text("long blob");
+      break;
+    case FIELD_TYPE_BLOB:
+      push_text("blob");
+      break;
+    case FIELD_TYPE_VAR_STRING:
+      push_text("var string");
+      break;
+    case FIELD_TYPE_STRING:
+      push_text("string");
+      break;
+    default:
+      push_text("unknown");
+      break;
+    }
+    push_text("length"); push_int(field->length);
+    push_text("max_length"); push_int(field->max_length);
+
+    push_text("flags");
+    if (IS_PRI_KEY(field->flags)) {
+      nbits++;
+      push_text("primary_key");
+    }
+    if (IS_NOT_NULL(field->flags)) {
+      nbits++;
+      push_text("not_null");
+    }
+    if (IS_BLOB(field->flags)) {
+      nbits++;
+      push_text("blob");
+    }
+    f_aggregate_multiset(nbits);
+
+    push_text("decimals"); push_int(field->decimals);
+      
+    if (support_default) {
+      f_aggregate_mapping(8*2);
+    } else {
+      f_aggregate_mapping(7*2);
+    }
+  } else {
+    /*
+     * Should this be an error?
+     */
+    push_int(0);
+  }
+}
+
+/*
+ * Methods
+ */
+
+/* void create(object(mysql)) */
+static void f_create(INT32 args)
+{
+  if (!args) {
+    error("Too few arguments to mysql_result()\n");
+  }
+  if ((sp[-args].type != T_OBJECT) ||
+      (sp[-args].u.object->prog != mysql_program)) {
+    error("Bad argument 1 to mysql_result()\n");
+  }
+
+  PIKE_MYSQL_RES->result = ((struct precompiled_mysql *)sp[-args].u.object->storage)->last_result;
+  ((struct precompiled_mysql *)sp[-args].u.object->storage)->last_result = NULL;
+  
+  pop_n_elems(args);
+
+  if (!PIKE_MYSQL_RES->result) {
+    error("mysql_result(): No result to clone\n");
+  }
+}
+
+/* int num_rows() */
+static void f_num_rows(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(mysql_num_rows(PIKE_MYSQL_RES->result));
+}
+
+/* int num_fields() */
+static void f_num_fields(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(mysql_num_fields(PIKE_MYSQL_RES->result));
+}
+
+#ifdef SUPPORT_FIELD_SEEK
+
+/* void field_seek(int fieldno) */
+static void f_field_seek(INT32 args)
+{
+  if (!args) {
+    error("Too few arguments to mysql->field_seek()\n");
+  }
+  if (sp[-args].type != T_INT) {
+    error("Bad argument 1 to mysql->field_seek()\n");
+  }
+  mysql_field_seek(PIKE_MYSQL_RES->result, sp[-args].u.integer);
+  pop_n_elems(args);
+}
+
+#endif /* SUPPORT_FIELD_SEEK */
+
+/* int eof() */
+static void f_eof(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(mysql_eof(PIKE_MYSQL_RES->result));
+}
+
+#ifdef SUPPORT_FIELD_SEEK
+
+/* int|mapping(string:mixed) fetch_field() */
+static void f_fetch_field(INT32 args)
+{
+  MYSQL_FIELD *field;
+  MYSQL_RES *res = PIKE_MYSQL_RES->result;
+
+  pop_n_elems(args);
+
+  THREADS_ALLOW();
+
+  field = mysql_fetch_field(res);
+
+  THREADS_DISALLOW();
+
+  mysqlmod_parse_field(field, 0);
+}
+
+#endif /* SUPPORT_FIELD_SEEK */
+
+/* array(int|mapping(string:mixed)) fetch_fields() */
+static void f_fetch_fields(INT32 args)
+{
+  MYSQL_FIELD *field;
+  int i = 0;
+  
+  pop_n_elems(args);
+
+  while ((field = mysql_fetch_field(PIKE_MYSQL_RES->result))) {
+    mysqlmod_parse_field(field, 0);
+    i++;
+  }
+  f_aggregate(i);
+
+  mysql_field_seek(PIKE_MYSQL_RES->result, 0);
+}
+
+/* void seek(int row) */
+static void f_seek(INT32 args)
+{
+  if (!args) {
+    error("Too few arguments to mysql_result->seek()\n");
+  }
+  if (sp[-args].type != T_INT) {
+    error("Bad argument 1 to mysql_result->seek()\n");
+  }
+  if (sp[-args].u.integer < 0) {
+    error("Negative argument 1 to mysql_result->seek()\n");
+  }
+  mysql_data_seek(PIKE_MYSQL_RES->result, sp[-args].u.integer);
+
+  pop_n_elems(args);
+}
+
+/* int|array(string|float|int) fetch_row() */
+static void f_fetch_row(INT32 args)
+{
+  int num_fields = mysql_num_fields(PIKE_MYSQL_RES->result);
+  MYSQL_ROW row = mysql_fetch_row(PIKE_MYSQL_RES->result);
+#ifdef HAVE_MYSQL_FETCH_LENGTHS
+  int *row_lengths = mysql_fetch_lengths(PIKE_MYSQL_RES->result);
+#endif /* HAVE_MYSQL_FETCH_LENGTHS */
+
+  pop_n_elems(args);
+
+  mysql_field_seek(PIKE_MYSQL_RES->result, 0);
+
+  if ((num_fields > 0) && row) {
+    int i;
+
+    for (i=0; i < num_fields; i++) {
+      if (row[i]) {
+	MYSQL_FIELD *field;
+
+	if (field = mysql_fetch_field(PIKE_MYSQL_RES->result)) {
+	  switch (field->type) {
+	    /* Integer types */
+	  case FIELD_TYPE_SHORT:
+	  case FIELD_TYPE_LONG:
+	  case FIELD_TYPE_INT24:
+#if 0
+	    /* This one will not always fit in an INT32 */
+          case FIELD_TYPE_LONGLONG:
+#endif /* 0 */
+	    push_int(atoi(row[i]));
+	    break;
+	    /* Floating point types */
+	  case FIELD_TYPE_DECIMAL:	/* Is this a float or an int? */
+	  case FIELD_TYPE_FLOAT:
+	  case FIELD_TYPE_DOUBLE:
+	    push_float(atof(row[i]));
+	    break;
+	  default:
+#ifdef HAVE_MYSQL_FETCH_LENGTHS
+	    push_string(make_shared_binary_string(row[i], row_lengths[i]));
+#else
+	    push_text(row[i]);
+#endif /* HAVE_MYSQL_FETCH_LENGTHS */
+	    break;
+	  }
+	} else {
+	  /* Probably doesn't happen, but... */
+#ifdef HAVE_MYSQL_FETCH_LENGTHS
+	  push_string(make_shared_binary_string(row[i], row_lengths[i]));
+#else
+	  push_text(row[i]);
+#endif /* HAVE_MYSQL_FETCH_LENGTHS */
+	}
+      } else {
+	/* NULL? */
+	push_int(0);
+      }
+    }
+    f_aggregate(num_fields);
+  } else {
+    /* No rows left in result */
+    push_int(0);
+  }
+
+  mysql_field_seek(PIKE_MYSQL_RES->result, 0);
+}
+
+/*
+ * Module linkage
+ */
+
+
+void init_mysql_res_programs(void)
+{
+  /*
+   * start_new_program();
+   *
+   * add_storage();
+   *
+   * add_function();
+   * add_function();
+   * ...
+   *
+   * set_init_callback();
+   * set_exit_callback();
+   *
+   * program = end_c_program();
+   * program->refs++;
+   *
+   */
+ 
+  start_new_program();
+  add_storage(sizeof(struct precompiled_mysql_result));
+
+  add_function("create", f_create, "function(object:void)", OPT_SIDE_EFFECT);
+  add_function("num_rows", f_num_rows, "function(void:int)", OPT_EXTERNAL_DEPEND);
+  add_function("num_fields", f_num_fields, "function(void:int)", OPT_EXTERNAL_DEPEND);
+#ifdef SUPPORT_FIELD_SEEK
+  add_function("field_seek", f_field_seek, "function(int:void)", OPT_SIDE_EFFECT);
+#endif /* SUPPORT_FIELD_SEEK */
+  add_function("eof", f_eof, "function(void:int)", OPT_EXTERNAL_DEPEND);
+#ifdef SUPPORT_FIELD_SEEK
+  add_function("fetch_field", f_fetch_field, "function(void:int|mapping(string:mixed))", OPT_EXTERNAL_DEPEND);
+#endif /* SUPPORT_FIELD_SEEK */
+  add_function("fetch_fields", f_fetch_fields, "function(void:array(int|mapping(string:mixed)))", OPT_EXTERNAL_DEPEND);
+  add_function("seek", f_seek, "function(int:void)", OPT_SIDE_EFFECT);
+  add_function("fetch_row", f_fetch_row, "function(void:int|array(string|int|float))", OPT_EXTERNAL_DEPEND|OPT_SIDE_EFFECT);
+
+  set_init_callback(init_res_struct);
+  set_exit_callback(exit_res_struct);
+
+  mysql_result_program = end_program();
+  add_program_constant("mysql_result",mysql_result_program, 0);
+}
+
+void exit_mysql_res(void)
+{
+  if (mysql_result_program) {
+    free_program(mysql_result_program);
+    mysql_result_program = NULL;
+  }
+}
+
+#endif /* HAVE_MYSQL */
diff --git a/src/modules/Pipe/.cvsignore b/src/modules/Pipe/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..47c84b07459ebcb78e36ee8c2e1a30cee7f440ff
--- /dev/null
+++ b/src/modules/Pipe/.cvsignore
@@ -0,0 +1,8 @@
+.pure
+Makefile
+config.h
+config.log
+config.status
+configure
+dependencies
+stamp-h
diff --git a/src/modules/Pipe/.gitignore b/src/modules/Pipe/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b27587e1b501d0baa775ec479e1a69e7da31aef0
--- /dev/null
+++ b/src/modules/Pipe/.gitignore
@@ -0,0 +1,8 @@
+/.pure
+/Makefile
+/config.h
+/config.log
+/config.status
+/configure
+/dependencies
+/stamp-h
diff --git a/src/modules/Pipe/Makefile.in b/src/modules/Pipe/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..2f114663c728caf7b6e11bb301fe8a705f77db0e
--- /dev/null
+++ b/src/modules/Pipe/Makefile.in
@@ -0,0 +1,6 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+OBJS=pipe.o
+
+@dynamic_module_makefile@
+@dependencies@
diff --git a/src/modules/Pipe/config.h.in b/src/modules/Pipe/config.h.in
new file mode 100644
index 0000000000000000000000000000000000000000..508a935ac246d6d24a59b2a5c7117feb8fc9ef50
--- /dev/null
+++ b/src/modules/Pipe/config.h.in
@@ -0,0 +1,13 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define if you have the mmap function.  */
+#undef HAVE_MMAP
+
+/* Define if you have the munmap function.  */
+#undef HAVE_MUNMAP
+
+/* Define if you have the <linux/mman.h> header file.  */
+#undef HAVE_LINUX_MMAN_H
+
+/* Define if you have the <sys/mman.h> header file.  */
+#undef HAVE_SYS_MMAN_H
diff --git a/src/modules/Pipe/configure.in b/src/modules/Pipe/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..84dd753063f827919780d8d80eb1b3967a264fdf
--- /dev/null
+++ b/src/modules/Pipe/configure.in
@@ -0,0 +1,11 @@
+AC_INIT(pipe.c)
+AC_CONFIG_HEADER(config.h)
+
+sinclude(../module_configure.in)
+
+AC_HAVE_HEADERS(sys/mman.h linux/mman.h)
+AC_HAVE_FUNCS(mmap munmap)
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
+
+
diff --git a/src/modules/Pipe/pipe.c b/src/modules/Pipe/pipe.c
new file mode 100644
index 0000000000000000000000000000000000000000..3158285191ab98ad50ed1f7257b4d792a6caf701
--- /dev/null
+++ b/src/modules/Pipe/pipe.c
@@ -0,0 +1,1142 @@
+#include <config.h>
+#include "machine.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#else
+#ifdef HAVE_LINUX_MMAN_H
+#include <linux/mman.h>
+#else
+#ifdef HAVE_MMAP
+/* sys/mman.h is _probably_ there anyway. */
+#include <sys/mman.h>
+#endif
+#endif
+#endif
+
+#include <fcntl.h>
+
+#include "global.h"
+RCSID("$Id: pipe.c,v 1.1 1997/02/11 08:38:08 hubbe Exp $");
+
+#include "stralloc.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "constants.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "error.h"
+
+#ifndef S_ISREG
+#ifdef S_IFREG
+#define S_ISREG(mode)   (((mode) & (S_IFMT)) == (S_IFREG))
+#else
+#define S_ISREG(mode)   (((mode) & (_S_IFMT)) == (_S_IFREG))
+#endif
+#endif
+
+/*
+#define PIPE_STRING_DEBUG
+#define PIPE_MMAP_DEBUG
+#define PIPE_FILE_DEBUG
+#define BLOCKING_CLOSE
+*/
+
+#if 0
+#define INSISTANT_WRITE
+#endif
+
+#ifndef MAP_FILE
+#  define MAP_FILE 0
+#endif
+
+#define READ_BUFFER_SIZE 32768
+#define MAX_BYTES_IN_BUFFER 65536
+
+/*
+ *  usage:
+ *  single socket output  
+ *  or regular file output and (multiple, adding) socket output 
+ *     with no mmap input 
+ *
+ *  multiple socket output without regular file output illegal
+ */
+
+static struct program *pipe_program, *output_program;
+
+#define THIS ((struct pipe *)(fp->current_storage))
+#define THISOBJ (fp->current_object)
+
+struct input
+{
+  enum { I_NONE,I_OBJ,I_STRING,I_MMAP } type;
+  union
+  {
+    struct object *obj; 
+    struct pike_string *str;
+    char *mmap;
+  } u;
+  unsigned long len;		/* current input: string or mmap len */
+  int set_blocking_offset, set_nonblocking_offset;
+  struct input *next;
+};
+
+struct output
+{
+  struct object *obj;
+  int write_offset, set_blocking_offset, set_nonblocking_offset;
+  int fd;
+      
+  enum 
+  { 
+    O_RUN,			/* waiting for callback */
+    O_SLEEP			/* sleeping; waiting for more data */
+  } mode;
+
+  unsigned long pos; /* position in buffer */
+  struct object *next;
+  struct pipe *the_pipe;
+};
+
+struct buffer
+{
+  struct pike_string *s;
+  struct buffer *next;
+};
+
+struct pipe
+{
+  int living_outputs;		/* number of output objects */
+
+  struct svalue done_callback;
+  struct svalue output_closed_callback;
+  struct svalue id;
+
+  /*
+   *  if fd is -1: use fd
+   *  else firstinput's type is I_MMAP: use firstinput's mmap
+   *  else use buffer
+   */
+
+  int fd;			/* buffer fd or -1 */
+
+  unsigned long bytes_in_buffer;
+  unsigned long pos; 
+  /* fd: size of buffer file */
+  /* current position of first element (buffer or mmap) */
+  struct buffer *firstbuffer,*lastbuffer;
+  short sleeping;			/* sleeping; buffer is full */
+  short done;
+  struct input *firstinput,*lastinput;
+  struct object *firstoutput;
+};
+
+static int offset_input_read_callback;
+static int offset_input_close_callback;
+static int offset_output_write_callback;
+static int offset_output_close_callback;
+static int mmapped, nobjects, nstrings, noutputs, ninputs, nbuffers, sbuffers;
+
+static char static_buffer[READ_BUFFER_SIZE];
+
+void close_and_free_everything(struct object *o,struct pipe *);
+static INLINE void output_finish(struct object *obj);
+static INLINE void output_try_write_some(struct object *obj);
+
+/********** internal ********************************************************/
+
+/* Push a callback to this object given the internal function number.
+ */
+static void push_callback(int no)
+{
+  sp->u.object=THISOBJ;
+  THISOBJ->refs++;
+  sp->subtype=no+fp->context.identifier_level;
+  sp->type=T_FUNCTION;
+  sp++;
+}
+
+/* Allocate a new struct input, link it last in the linked list */
+static INLINE struct input *new_input(void)
+{
+  struct input *i;
+  ninputs++;
+  i=ALLOC_STRUCT(input);
+  i->type=I_NONE;
+  i->next=NULL;
+  if (THIS->lastinput)
+    THIS->lastinput->next=i;
+  else
+    THIS->firstinput=i;
+  THIS->lastinput=i;
+  return i;
+}
+
+/* Free an input struct and all that it stands for */
+static INLINE void free_input(struct input *i)
+{
+  ninputs--;
+  switch (i->type)
+  {
+  case I_OBJ:
+    if (!i->u.obj) break;
+    if (i->u.obj->prog)
+    {
+#ifdef BLOCKING_CLOSE
+      apply_low(i->u.obj,i->set_blocking_offset,0);
+      pop_stack();
+#endif
+      apply(i->u.obj,"close",0);
+      pop_stack();
+      destruct(i->u.obj);
+    }
+    free_object(i->u.obj);
+    nobjects--;
+    i->u.obj=0;
+    break;
+
+  case I_STRING:
+    free_string(i->u.str);
+    nstrings--;
+    break;
+
+  case I_MMAP:
+#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+    munmap(i->u.mmap,i->len);
+    mmapped -= i->len;  
+#else
+    error("I_MMAP input when MMAP is diabled!");
+#endif
+    break;
+  }
+  free((char *)i);
+}
+
+/* do the done_callback, then close and free everything */
+static INLINE void pipe_done(void)
+{
+  if (THIS->done_callback.type!=T_INT)
+  {
+    assign_svalue_no_free(sp++,&THIS->id);
+    apply_svalue(&(THIS->done_callback),1);
+    pop_stack();
+
+    if(!THISOBJ->prog) /* We will not free anything in this case. */
+      return;
+    /*  error("Pipe done callback destructed pipe.\n");  */
+  }
+  close_and_free_everything(THISOBJ,THIS);
+}
+
+static void finished_p(void)
+{
+  if(THIS->done) return;
+
+  if(THIS->fd != -1) 
+  {
+    if(THIS->living_outputs > 1) return;
+    if(THIS->firstinput) return;
+
+  }else{
+    if(THIS->living_outputs) return;
+  }
+  pipe_done();
+}
+
+/* Allocate a new buffer and put it at the end of the chain of buffers
+ * scheduled for output. Return 1 if we have more bytes in buffers
+ * than allowed afterwards.
+ */
+static INLINE int append_buffer(struct pike_string *s)
+   /* 1=buffer full */
+{
+   struct buffer *b;
+
+   if(THIS->fd!= -1)
+   {
+     lseek(THIS->fd,THIS->pos,0);
+     write(THIS->fd,s->str,s->len);
+     THIS->pos+=s->len;
+     return 0;
+   }
+   else
+   {
+     nbuffers++;
+     b=ALLOC_STRUCT(buffer);
+     b->next=NULL;
+     b->s=s;
+     sbuffers += s->len;
+     s->refs++;
+
+     if (THIS->lastbuffer)
+       THIS->lastbuffer->next=b;
+     else
+       THIS->firstbuffer=b;
+
+     THIS->lastbuffer=b;
+
+     THIS->bytes_in_buffer+=s->len;
+   }
+   return THIS->bytes_in_buffer > MAX_BYTES_IN_BUFFER;
+}
+
+/* Wake up the sleepers */
+static void low_start()
+{
+  struct object *obj, *next;
+  struct output *o;
+
+
+  THISOBJ->refs++;		/* dont kill yourself now */
+  for(obj=THIS->firstoutput;obj;obj=next)
+  {
+    obj->refs++; /* Hang on PLEASE!! /hubbe */
+    o=(struct output *)(obj->storage);
+    if (o->obj && o->mode==O_SLEEP)
+    {
+      if (!o->obj->prog)
+      {
+	output_finish(obj);
+      }
+      else
+      {
+#if 0
+	push_int(0);
+	push_callback(offset_output_write_callback);
+	push_callback(offset_output_close_callback);
+	apply_low(o->obj,o->set_nonblocking_offset,3);
+#endif
+	output_try_write_some(obj);
+	o->mode=O_RUN;		/* Hubbe */
+      }
+    }
+    next=o->next;
+    free_object(obj);
+  }
+
+  free_object(THISOBJ);
+}
+
+/* Let's guess what this function does....
+ *
+ */
+static INLINE void input_finish(void)
+{
+  struct input *i;
+
+  while(1)
+  {
+    i=THIS->firstinput->next;
+    free_input(THIS->firstinput);
+    THIS->firstinput=i;
+
+    if(!i) break;
+
+    switch(i->type)
+    {
+    case I_OBJ:
+      THIS->sleeping=0;
+      push_callback(offset_input_read_callback);
+      push_int(0);
+      push_callback(offset_input_close_callback);
+      apply_low(i->u.obj,i->set_nonblocking_offset,3);
+      pop_stack();
+      return;
+
+    case I_MMAP:
+      if (THIS->fd==-1) return;
+      continue;
+
+    case I_STRING:
+      append_buffer(i->u.str);
+    }
+  }
+  THIS->sleeping=0;
+
+  low_start();
+  finished_p();
+}
+
+/* This function reads some data from the file cache..
+ * Called when we want some data to send.
+ */
+static INLINE struct pike_string* gimme_some_data(unsigned long pos)
+{
+   struct buffer *b;
+   unsigned long len;
+
+   /* We have a file cache, read from it */
+   if (THIS->fd!=-1)
+   {
+      if (THIS->pos<=pos) return NULL; /* no data */
+      len=THIS->pos-pos;
+      if (len>READ_BUFFER_SIZE) len=READ_BUFFER_SIZE;
+      lseek(THIS->fd,pos,0); /* SEEK_SET */
+      read(THIS->fd,static_buffer,len);
+      return make_shared_binary_string(static_buffer,len);
+   }
+
+   if (pos<THIS->pos)
+     return make_shared_string("buffer underflow"); /* shit */
+
+   /* We want something in the next buffer */
+   while (THIS->firstbuffer && pos>=THIS->pos+THIS->firstbuffer->s->len) 
+   {
+     /* Free the first buffer, and update THIS->pos */
+      b=THIS->firstbuffer;
+      THIS->pos+=b->s->len;
+      THIS->bytes_in_buffer-=b->s->len;
+      THIS->firstbuffer=b->next;
+      if (!b->next)
+	THIS->lastbuffer=NULL;
+      sbuffers-=b->s->len;
+      nbuffers--;
+      free_string(b->s);
+      free((char *)b);
+
+      /* Wake up first input if it was sleeping and we
+       * have room for more in the buffer.
+       */
+      if (THIS->sleeping &&
+	  THIS->firstinput &&
+	  THIS->bytes_in_buffer<MAX_BYTES_IN_BUFFER)
+      {
+	THIS->sleeping=0;
+	push_callback(offset_input_read_callback);
+	push_int(0);
+	push_callback(offset_input_close_callback);
+	apply(THIS->firstinput->u.obj, "set_nonblocking", 3);
+	pop_stack();
+      }
+   }
+
+   while (!THIS->firstbuffer)
+   {
+     if (THIS->firstinput)
+     {
+#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+       if (THIS->firstinput->type==I_MMAP)
+       {
+	 if (pos >= THIS->firstinput->len + THIS->pos) /* end of mmap */
+	 {
+	   THIS->pos+=THIS->firstinput->len;
+	   input_finish();
+	   continue;
+	 }
+	 len=THIS->firstinput->len+THIS->pos-pos;
+	 if (len>READ_BUFFER_SIZE) len=READ_BUFFER_SIZE;
+	 return make_shared_binary_string(THIS->firstinput->u.mmap+
+					  pos-THIS->pos,
+					  len);
+       }
+       else
+#endif
+       if (THIS->firstinput->type!=I_OBJ)
+       {
+	 input_finish();       /* shouldn't be anything else ... maybe a finished object */
+       }
+     }
+     return NULL;		/* no data */
+   } 
+
+   if (pos==THIS->pos)
+   {
+      THIS->firstbuffer->s->refs++;
+      return THIS->firstbuffer->s;
+   }
+   return make_shared_binary_string(THIS->firstbuffer->s->str+
+				    pos-THIS->pos,
+				    THIS->firstbuffer->s->len-
+				    pos+THIS->pos);
+}
+
+
+/*
+ * close and free the contents of a struct output
+ * Note that the output struct is not freed or unlinked here,
+ * that is taken care of later.
+ */
+static INLINE void output_finish(struct object *obj)
+{
+  struct output *o;
+
+  o=(struct output *)(obj->storage);
+
+  if (o->obj)
+  {
+    if(o->obj->prog)
+    {
+#ifdef BLOCKING_CLOSE
+      apply_low(o->obj,o->set_blocking_offset,0);
+      pop_stack();
+#endif
+      push_int(0);
+      apply(o->obj,"set_id",1);
+      pop_stack();
+
+      apply(o->obj,"close",0);
+      pop_stack();
+      if(!THISOBJ->prog)
+	error("Pipe done callback destructed pipe.\n");
+      destruct(o->obj);
+    }
+    free_object(o->obj);
+    noutputs--;
+    o->obj=NULL;
+
+    THIS->living_outputs--;
+
+    finished_p(); /* Moved by per, one line down.. :) */
+
+    free_object(THISOBJ);		/* What? /Hubbe */
+  }
+}
+
+/*
+ * Try to write some data to our precious output
+ */
+static INLINE void output_try_write_some(struct object *obj)
+{
+  struct output *out;
+  struct pike_string *s;
+  unsigned long len;
+  INT32 ret;
+  
+  out=(struct output*)(obj->storage);
+
+#ifdef INSISTANT_WRITE   
+  do
+  {
+#endif
+    /* Get some data to write */
+    s=gimme_some_data(out->pos);
+    if (!s)			/* out of data */
+    {
+      /* out of data, goto sleep */
+      if (!THIS->firstinput || !out->obj->prog) /* end of life */
+      {
+	output_finish(obj);
+      }
+      else
+      {
+#if 0
+	apply_low(out->obj, out->set_blocking_offset, 0);
+	pop_stack();		/* from apply */
+#endif
+	out->mode=O_SLEEP;
+      }
+      return;
+    }
+    len=s->len;
+    push_string(s);
+    apply_low(out->obj,out->write_offset,1);
+    out->mode=O_RUN;
+
+    ret=-1;
+    if(sp[-1].type == T_INT) ret=sp[-1].u.integer;
+    pop_stack();
+
+    if (ret==-1)		/* error, byebye */
+    {
+      output_finish(obj);
+      return;
+    }
+    out->pos+=ret;
+
+#ifdef INSISTANT_WRITE
+  } while(ret == len);
+#endif
+}
+
+/********** methods *********************************************************/
+
+/* Add an input to this pipe */
+static void pipe_input(INT32 args)
+{
+   struct input *i;
+   int fd=-1;			/* Per, one less warning to worry about... */
+   char *m;
+   struct stat s;
+   struct object *obj;
+
+   if (args<1 || sp[-args].type != T_OBJECT)
+     error("Bad/missing argument 1 to pipe->input().\n");
+
+   obj=sp[-args].u.object;
+   if(!obj || !obj->prog)
+     error("pipe->input() on destructed object.\n");
+
+   push_int(0);
+   apply(sp[-args-1].u.object,"set_id", 1);
+   pop_stack();
+
+   i=new_input();
+
+#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+
+   /* We do not handle mmaps if we have a buffer */
+   if(THIS->fd == -1)
+   {
+     apply(obj, "query_fd", 0);
+     if(sp[-1].type == T_INT) fd=sp[-1].u.integer;
+     pop_stack();
+
+     if (fd != -1 && fstat(fd,&s)==0)
+     {
+       if(S_ISREG(s.st_mode)	/* regular file */
+	  && ((long)(m=(char *)mmap(0,s.st_size,PROT_READ,
+				    MAP_FILE|MAP_SHARED,fd,0))!=-1))
+       {
+	 mmapped += s.st_size;
+
+	 i->type=I_MMAP;
+	 i->len=s.st_size;
+	 i->u.mmap=m;
+       
+	 pop_n_elems(args);
+	 push_int(0);
+	 return;
+       }
+     }
+   }
+#endif
+
+   i->u.obj=obj;
+   nobjects++;
+   i->type=I_OBJ;
+   i->u.obj->refs++;
+   i->set_nonblocking_offset=find_identifier("set_nonblocking",i->u.obj->prog);
+   i->set_blocking_offset=find_identifier("set_blocking",i->u.obj->prog);
+
+   if (i->set_nonblocking_offset<0 ||
+       i->set_blocking_offset<0) 
+   {
+      free_object(i->u.obj);
+      i->u.obj=NULL;
+      i->type=I_NONE;
+
+      nobjects--;
+      error("illegal file object%s%s\n",
+	    ((i->set_nonblocking_offset<0)?"; no set_nonblocking":""),
+	    ((i->set_blocking_offset<0)?"; no set_blocking":""));
+   }
+  
+   if (i==THIS->firstinput)
+   {
+     push_callback(offset_input_read_callback);
+     push_int(0);
+     push_callback(offset_input_close_callback);
+     apply_low(i->u.obj,i->set_nonblocking_offset,3);
+     pop_stack();
+   }
+   else
+   {
+     /* DOESN'T WORK!!! */
+     push_int(0);
+     push_int(0);
+     push_callback(offset_input_close_callback);
+     apply_low(i->u.obj,i->set_nonblocking_offset,3);
+     pop_stack();
+   }
+
+   pop_n_elems(args);
+   push_int(0);
+}
+
+static void pipe_write(INT32 args)
+{
+  struct input *i;
+
+  if (args<1 || sp[-args].type!=T_STRING)
+    error("illegal argument to pipe->write()\n");
+
+  if (!THIS->firstinput)
+  {
+    append_buffer(sp[-args].u.string);
+    pop_n_elems(args);
+    push_int(0);
+    return;
+  }
+
+  i=new_input();
+  i->type=I_STRING;
+  nstrings++;
+  i->u.str=sp[-args].u.string;
+  i->u.str->refs++;
+  pop_n_elems(args-1);
+}
+
+void f_mark_fd(INT32 args);
+
+static void pipe_output(INT32 args)
+{
+  struct object *obj;
+  struct output *o;
+  int fd;
+  struct stat s;
+  struct buffer *b;
+
+  if (args<1 || 
+      sp[-args].type != T_OBJECT ||
+      !sp[-args].u.object ||
+      !sp[-args].u.object->prog)
+    error("Bad/missing argument 1 to pipe->output().\n");
+
+  if (THIS->fd==-1)		/* no buffer */
+  {
+    /* test if usable as buffer */ 
+    apply(sp[-args].u.object,"query_fd",0);
+
+    if ((sp[-1].type==T_INT)
+	&& (fd=sp[-1].u.integer)>=0
+	&& (fstat(fd,&s)==0)
+	&& S_ISREG(s.st_mode)
+	&& (THIS->fd=dup(fd))!=-1 )
+    {
+      push_int(THIS->fd);
+      push_string(make_shared_string("pipe.c: file buffer"));
+      f_mark_fd(2);
+      pop_stack();
+
+      THIS->living_outputs++;
+
+      while (THIS->firstbuffer)
+      {
+	b=THIS->firstbuffer;
+	THIS->firstbuffer=b->next;
+	lseek(THIS->fd,THIS->pos,0);
+	write(THIS->fd,b->s->str,b->s->len);
+	sbuffers-=b->s->len;
+	nbuffers--;
+	free_string(b->s);
+	free((char *)b);
+      }
+      THIS->lastbuffer=NULL;
+
+      THIS->pos=0;
+      push_int(0);
+      apply(sp[-args-2].u.object,"set_id", 1);
+      pop_n_elems(args+2);	/* ... and from apply x 2  */
+      return;
+    }
+    pop_stack();		/* from apply */
+  } 
+
+  THIS->living_outputs++;
+  THISOBJ->refs++;		/* Weird */
+
+  /* Allocate a new struct output */
+  obj=clone(output_program,0);
+  o=(struct output *)(obj->storage);
+  o->next=THIS->firstoutput;
+  THIS->firstoutput=obj;
+  noutputs++;
+  o->obj=NULL;
+
+  o->obj=sp[-args].u.object;
+  o->obj->refs++;
+
+  o->write_offset=find_identifier("write",o->obj->prog);
+  o->set_nonblocking_offset=find_identifier("set_nonblocking",o->obj->prog);
+  o->set_blocking_offset=find_identifier("set_blocking",o->obj->prog);
+
+  if (o->write_offset<0 || o->set_nonblocking_offset<0 ||
+      o->set_blocking_offset<0) 
+  {
+    free_object(o->obj);
+    error("illegal file object%s%s%s\n",
+	  ((o->write_offset<0)?"; no write":""),
+	  ((o->set_nonblocking_offset<0)?"; no set_nonblocking":""),
+	  ((o->set_blocking_offset<0)?"; no set_blocking":""));
+  }
+
+  o->mode=O_RUN;
+  o->pos=0;
+
+  push_object(obj);
+  obj->refs++;
+  apply(o->obj,"set_id",1);
+  pop_stack();
+
+  push_int(0);
+  push_callback(offset_output_write_callback);
+  push_callback(offset_output_close_callback);
+  apply_low(o->obj,o->set_nonblocking_offset,3);
+  pop_stack();
+   
+  pop_n_elems(args-1);
+}
+
+static void pipe_set_done_callback(INT32 args)
+{
+  if (args==0)
+  {
+    free_svalue(&THIS->done_callback);
+    THIS->done_callback.type=T_INT;
+    return;
+  }
+  if (args<1 || (sp[-args].type!=T_FUNCTION && sp[-args].type!=T_ARRAY))
+    error("Illegal argument to set_done_callback()\n");
+
+  if (args>1)
+  {
+     free_svalue(&THIS->id);
+     assign_svalue_no_free(&(THIS->id),sp-args+1);
+  }
+
+  free_svalue(&THIS->done_callback);
+  assign_svalue_no_free(&(THIS->done_callback),sp-args); 
+  pop_n_elems(args-1); 
+}
+
+static void pipe_set_output_closed_callback(INT32 args)
+{
+  if (args==0)
+  {
+    free_svalue(&THIS->done_callback);
+    THIS->output_closed_callback.type=T_INT;
+    return;
+  }
+  if (args<1 || (sp[-args].type!=T_FUNCTION && sp[-args].type!=T_ARRAY))
+    error("Illegal argument to set_output_closed_callback()\n");
+
+  if (args>1)
+  {
+     free_svalue(&THIS->id);
+     assign_svalue_no_free(&(THIS->id),sp-args+1);
+  }
+  free_svalue(&THIS->output_closed_callback);
+  assign_svalue_no_free(&(THIS->output_closed_callback),sp-args); 
+  pop_n_elems(args-1); 
+}
+
+static void pipe_finish(INT32 args)
+{
+   pop_n_elems(args);
+   push_int(0);
+   pipe_done();
+}
+
+static void pipe_start(INT32 args) /* force start */
+{
+  low_start();
+  if(args)
+    pop_n_elems(args-1);
+}
+
+/********** callbacks *******************************************************/
+
+static void pipe_write_output_callback(INT32 args)
+{
+   if (args<1 || sp[-args].type!=T_OBJECT)
+     error("Illegal argument to pipe->write_output_callback\n");
+
+   if(!sp[-args].u.object->prog) return;
+
+   if(sp[-args].u.object->prog != output_program)
+     error("Illegal argument to pipe->write_output_callback\n");
+
+   output_try_write_some(sp[-args].u.object);
+   pop_n_elems(args-1);
+}
+
+static void pipe_close_output_callback(INT32 args)
+{
+  struct output *o;
+   if (args<1 || sp[-args].type!=T_OBJECT)
+
+   if(!sp[-args].u.object->prog) return;
+
+   if(sp[-args].u.object->prog != output_program)
+     error("Illegal argument to pipe->close_output_callback\n");
+
+  o=(struct output *)(sp[-args].u.object->storage);
+
+  if (THIS->output_closed_callback.type!=T_INT)
+  {
+    assign_svalue_no_free(sp++,&THIS->id);
+    push_object(o->obj);
+    apply_svalue(&(THIS->output_closed_callback),2);
+    pop_stack();
+  }
+
+  output_finish(sp[-args].u.object);
+  pop_n_elems(args-1);
+}
+
+static void pipe_read_input_callback(INT32 args)
+{
+  struct input *i;
+  struct pike_string *s;
+
+  if (args<2 || sp[1-args].type!=T_STRING)
+    error("Illegal argument to pipe->read_input_callback\n");
+   
+  i=THIS->firstinput;
+
+  if (!i)
+    error("Pipe read callback without any inputs left.\n");
+
+  s=sp[1-args].u.string;
+
+  if(append_buffer(s))
+  {
+    /* THIS DOES NOT WORK */
+    push_int(0);
+    push_int(0);
+    push_callback(offset_input_close_callback);
+    apply_low(i->u.obj,i->set_nonblocking_offset,3);
+    pop_stack();
+    THIS->sleeping=1;
+  }
+
+  low_start();
+  pop_n_elems(args-1);
+}
+
+static void pipe_close_input_callback(INT32 args)
+{
+   struct input *i;
+   i=THIS->firstinput;
+
+   if(!i)
+     error("Input close callback without inputs!\n");
+
+   if(i->type != I_OBJ)
+     error("Premature close callback on pipe!.\n");
+
+   if (i->u.obj->prog)
+   {
+#ifdef BLOCKING_CLOSE
+      apply_low(i->u.obj,i->set_blocking_offset,0);
+      pop_stack();
+#endif
+      apply(i->u.obj,"close",0);
+      pop_stack();
+   }
+   nobjects--;
+   free_object(i->u.obj);
+   i->type=I_NONE;
+
+   input_finish();
+   if(args)
+     pop_n_elems(args-1);
+}
+
+static void pipe_version(INT32 args)
+{
+   pop_n_elems(args);
+   push_string(make_shared_string("PIPE ver 2.0"));
+}
+
+/********** init/exit *******************************************************/
+
+void close_and_free_everything(struct object *thisobj,struct pipe *p)
+{
+   struct buffer *b;
+   struct input *i;
+   struct output *o;
+   struct object *obj;
+   
+   p->done=1;
+
+   if (thisobj) 
+      thisobj->refs++; /* don't kill object during this */
+
+   while (p->firstbuffer)
+   {
+      b=p->firstbuffer;
+      p->firstbuffer=b->next;
+      sbuffers-=b->s->len;
+      nbuffers--;
+      free_string(b->s);
+      b->next=NULL;
+      free((char *)b); /* Hubbe */
+   }
+   p->lastbuffer=NULL;
+
+
+   while (p->firstinput)
+   {
+      i=p->firstinput;
+      p->firstinput=i->next;
+      free_input(i);
+   }
+   p->lastinput=NULL;
+
+   while (p->firstoutput)
+   {
+     obj=p->firstoutput;
+     o=(struct output *)(obj->storage);
+     p->firstoutput=o->next;
+     output_finish(obj);
+     free_object(obj);
+   }
+   if (p->fd!=-1)
+   {
+     close(p->fd);
+     p->fd=-1;
+   }
+
+   p->living_outputs=0;
+   
+   if (thisobj)
+     free_object(thisobj);
+
+   free_svalue(& p->done_callback);
+   free_svalue(& p->output_closed_callback);
+   free_svalue(& p->id);
+
+   p->done_callback.type=T_INT;
+   p->output_closed_callback.type=T_INT;
+   p->id.type=T_INT;
+
+   p->done=0;
+}
+
+static void init_pipe_struct(struct object *o)
+{
+   THIS->firstbuffer=THIS->lastbuffer=NULL;
+   THIS->firstinput=THIS->lastinput=NULL;
+   THIS->firstoutput=NULL;
+   THIS->bytes_in_buffer=0;
+   THIS->pos=0;
+   THIS->sleeping=0;
+   THIS->done=0;
+   THIS->fd=-1;
+   THIS->done_callback.type=T_INT;
+   THIS->output_closed_callback.type=T_INT;
+   THIS->id.type=T_INT;
+   THIS->id.u.integer=0;
+   THIS->living_outputs=0;
+}
+
+static void exit_pipe_struct(struct object *o)
+{
+  close_and_free_everything(NULL,THIS);
+}
+
+static void exit_output_struct(struct object *obj)
+{
+  struct output *o;
+  
+  o=(struct output *)(fp->current_storage);
+  if (o->obj)
+  {
+    if(o->obj->prog)
+    {
+#ifdef BLOCKING_CLOSE
+      apply_low(o->obj,o->set_blocking_offset,0);
+      pop_stack();
+#endif
+      push_int(0);
+      apply(o->obj,"set_id",1);
+      pop_stack();
+
+      apply(o->obj,"close",0);
+      pop_stack();
+
+      if(!THISOBJ->prog)
+	error("Pipe done callback destructed pipe.\n");
+    }
+    free_object(o->obj);
+    noutputs--;
+    o->obj=0;
+  }
+}
+
+static void init_output_struct(struct object *ob)
+{
+  struct output *o;
+  o=(struct output *)(fp->current_storage);
+  o->obj=0;
+}
+
+
+/********** Pike init *******************************************************/
+
+void port_setup_program(void);
+
+void f__pipe_debug(INT32 args)
+{
+  pop_n_elems(args);
+  push_int(noutputs);
+  push_int(ninputs);
+  push_int(nstrings);
+  push_int(nobjects);
+  push_int(mmapped);
+  push_int(nbuffers);
+  push_int(sbuffers);
+  f_aggregate(7);
+}
+
+void pike_module_init()
+{
+   start_new_program();
+   add_storage(sizeof(struct pipe));
+   add_efun("_pipe_debug", f__pipe_debug, "function(:array)", 0);
+   add_function("input",pipe_input,"function(object:void)",0);
+   add_function("output",pipe_output,"function(object:void)",0);
+   add_function("write",pipe_write,"function(string:void)",0);
+
+   add_function("start",pipe_start,"function(:void)",0);
+   add_function("finish",pipe_finish,"function(:void)",0);
+   
+   add_function("set_output_closed_callback",pipe_set_output_closed_callback,
+		"function(void|function(mixed,object:mixed),void|mixed:void)",0);
+   add_function("set_done_callback",pipe_set_done_callback,
+		"function(void|function(mixed:mixed),void|mixed:void)",0);
+   
+   add_function("_output_close_callback",pipe_close_output_callback,
+		"function(int:void)",0);
+   add_function("_input_close_callback",pipe_close_input_callback,
+		"function(int:void)",0);
+   add_function("_output_write_callback",pipe_write_output_callback,
+		"function(int:void)",0);
+   add_function("_input_read_callback",pipe_read_input_callback,
+		"function(int,string:void)",0);
+
+   add_function("version",pipe_version,"function(:string)",0);
+   
+   set_init_callback(init_pipe_struct);
+   set_exit_callback(exit_pipe_struct);
+   
+   pipe_program=end_program();
+   add_program_constant("pipe",pipe_program, 0);
+   
+   offset_output_close_callback=find_identifier("_output_close_callback",
+						pipe_program);
+   offset_input_close_callback=find_identifier("_input_close_callback",
+					       pipe_program);
+   offset_output_write_callback=find_identifier("_output_write_callback",
+						pipe_program);
+   offset_input_read_callback=find_identifier("_input_read_callback",
+					      pipe_program);
+
+
+   start_new_program();
+   add_storage(sizeof(struct output));
+   set_init_callback(init_output_struct);
+   set_exit_callback(exit_output_struct);
+   output_program=end_program();
+   add_program_constant("__output",output_program, 0);
+}
+
+void pike_module_exit(void) 
+{
+  if(pipe_program) free_program(pipe_program);
+  pipe_program=0;
+  if(output_program) free_program(output_program);
+  output_program=0;
+}
+
+
+
+
+
+
+
diff --git a/src/modules/Pipe/testsuite.in b/src/modules/Pipe/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/modules/Regexp/.cvsignore b/src/modules/Regexp/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..ed13e79dafb075dba9bee95ed9608b991ad17397
--- /dev/null
+++ b/src/modules/Regexp/.cvsignore
@@ -0,0 +1,7 @@
+.pure
+Makefile
+config.log
+config.status
+configure
+dependencies
+stamp-h
diff --git a/src/modules/Regexp/.gitignore b/src/modules/Regexp/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2d448ae74bf4fb36f7a1db409d228c656ce05d72
--- /dev/null
+++ b/src/modules/Regexp/.gitignore
@@ -0,0 +1,7 @@
+/.pure
+/Makefile
+/config.log
+/config.status
+/configure
+/dependencies
+/stamp-h
diff --git a/src/modules/Regexp/Makefile.in b/src/modules/Regexp/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..21853b9fc4c4f4ec9110cbd08caed4824af78aa6
--- /dev/null
+++ b/src/modules/Regexp/Makefile.in
@@ -0,0 +1,6 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+OBJS=regexp.o glue.o
+
+@dynamic_module_makefile@
+@dependencies@
\ No newline at end of file
diff --git a/src/modules/Regexp/configure.in b/src/modules/Regexp/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..ed3283c8eec415697210f9591f054dac166c5e4c
--- /dev/null
+++ b/src/modules/Regexp/configure.in
@@ -0,0 +1,8 @@
+AC_INIT(regexp.c)
+
+sinclude(../module_configure.in)
+
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
+
+
diff --git a/src/modules/Regexp/doc/regexp b/src/modules/Regexp/doc/regexp
new file mode 100644
index 0000000000000000000000000000000000000000..b7358e616d66fdccc1ae4354f73a8d14715dafa1
--- /dev/null
+++ b/src/modules/Regexp/doc/regexp
@@ -0,0 +1,91 @@
+NAME
+	/precompiled/regexp - regexp handling module
+
+DESCRIPTION
+	/precompiled/regexp is a precompiled Pike program that interfaces a
+	regexp package written in C. It contains a few simple functions to
+	handle regexps. A short description of regexp follows:
+
+	.	Matches any character
+	[abc]	Matches a, b or c
+	[a-z]	Matches any character a to z inclusive
+	[^ac]	Matches any character except a and c
+	(x)	Matches x (x might be any regexp) If used with split, this
+		also puts the string matching x into the result array.
+	x*	Matches zero or more occurances of 'x' (x may be any regexp)
+	x+	Matches one or more occurances of 'x' (x may be any regexp)
+	x|y	Matches x or y. (x or y may be any regexp)
+	xy	Matches xy (x and y may be any regexp)
+	^	Matches beginning of string (but no characters)
+	$	Matches end of string (but no characters)
+	\<	matches the beginning of a word (but no characters)
+	\>	matches the end of a word (but no characters)
+
+	Note that \ can be used to quote these characters in which case
+	they match themselves, nothing else. Also note that when quoting
+	these something in Pike you need two \ because Pike also uses
+	this character for quoting.
+
+	For more information about regexps, refer to your unix manuals such
+	as sed or ed.
+
+	Descriptions of all functions in /precompiled/regexp follows:
+
+============================================================================
+NAME
+	create - compile regexp
+
+SYNTAX
+	void create();
+	or
+	void create(string regexp);
+	or
+	object clone((program)"/precompiled/file");
+	or
+	object clone((program)"/precompiled/file",string regexp);
+
+DESCRIPTION
+	When create is called, the current regexp bound to this object is
+	cleared. If a string is sent to create(), this string will be compiled
+	to an internal representation of the regexp and bound to this object
+	for laters calls to match or split. Calling create() without an
+	argument can be used to free up a little memory after the regexp has
+	been used.
+
+SEE ALSO
+	builtin/clone, regexp->match
+
+============================================================================
+NAME
+	match - match a regexp
+
+SYNTAX
+	int regexp->match(string s)
+
+DESCRIPTION
+	Return 1 if s matches the regexp bound to the object regexp,
+	zero otherwise
+
+SEE ALSO
+	regexp->create, regexp->split
+
+============================================================================
+NAME
+	split - split a string according to a pattern
+
+SYNTAX
+	string *regexp->split(string s)
+
+DESCRIPTION
+	Works as regexp->match, but returns an array of the strings that
+	matched the subregexps. Subregexps are those contained in ( ) in
+	the regexp. Subregexps that were not matched will contain zero.
+	If the total regexp didn't match, zero is returned.
+
+BUGS
+	You can only have 40 subregexps.
+
+SEE ALSO
+	regexp->create, regexp->split
+
+============================================================================
diff --git a/src/modules/Regexp/glue.c b/src/modules/Regexp/glue.c
new file mode 100644
index 0000000000000000000000000000000000000000..9da6245d8aeb83cb7574379c8f0c5a69bab9b722
--- /dev/null
+++ b/src/modules/Regexp/glue.c
@@ -0,0 +1,120 @@
+/*\
+||| This file a part of Pike, and is copyright by Fredrik Hubinette
+||| Pike is distributed as GPL (General Public License)
+||| See the files COPYING and DISCLAIMER for more information.
+\*/
+#include "global.h"
+#include "types.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "stralloc.h"
+#include "array.h"
+#include "object.h"
+#include "macros.h"
+
+#include <regexp.h>
+
+struct regexp_glue
+{
+  struct regexp *regexp;
+};
+
+#define THIS ((struct regexp_glue *)(fp->current_storage))
+
+static void do_free()
+{
+  if(THIS->regexp)
+  {
+    free((char *)THIS->regexp);
+    THIS->regexp=0;
+  }
+}
+
+static void regexp_create(INT32 args)
+{
+  do_free();
+  if(args)
+  {
+    if(sp[-args].type != T_STRING)
+      error("Bad argument 1 to regexp->create()\n");
+
+    THIS->regexp=regcomp(sp[-args].u.string->str, 0);
+  }
+}
+
+static void regexp_match(INT32 args)
+{
+  int i;
+  if(!args)
+    error("Too few arguments to regexp->match()\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to regexp->match()\n");
+
+  i=regexec(THIS->regexp, sp[-args].u.string->str);
+  pop_n_elems(args);
+  push_int(i);
+}
+
+static void regexp_split(INT32 args)
+{
+  struct pike_string *s;
+  struct regexp *r;
+  if(!args)
+    error("Too few arguments to regexp->split()\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to regexp->split()\n");
+
+  s=sp[-args].u.string;
+  if(regexec(r=THIS->regexp, s->str))
+  {
+    int i,j;
+    s->refs++;
+    pop_n_elems(args);
+    for(j=i=1;i<NSUBEXP;i++)
+    {
+      if(!r->startp[i] || !r->endp[i])
+      {
+	push_int(0);
+      }else{
+	push_string(make_shared_binary_string(r->startp[i],
+					      r->endp[i]-r->startp[i]));
+	j=i;
+      }
+    }
+    if(j<i-1) pop_n_elems(i-j-1);
+    push_array(aggregate_array(j));
+    free_string(s);
+  }else{
+    pop_n_elems(args);
+    push_int(0);
+  }
+}
+
+static void init_regexp_glue(struct object *o)
+{
+  THIS->regexp=0;
+}
+
+static void exit_regexp_glue(struct object *o)
+{
+  do_free();
+}
+
+
+void pike_module_exit(void) {}
+
+void pike_module_init(void)
+{
+  start_new_program();
+  add_storage(sizeof(struct regexp_glue));
+  
+  add_function("create",regexp_create,"function(void|string:void)",0);
+  add_function("match",regexp_match,"function(string:int)",0);
+  add_function("split",regexp_split,"function(string:string*)",0);
+
+  set_init_callback(init_regexp_glue);
+  set_exit_callback(exit_regexp_glue);
+  end_class("_module_value", 0);
+}
diff --git a/src/modules/Regexp/pike_regexp.c b/src/modules/Regexp/pike_regexp.c
new file mode 100644
index 0000000000000000000000000000000000000000..08c988177cecbba9f3c2216dc13fc8095d3fa185
--- /dev/null
+++ b/src/modules/Regexp/pike_regexp.c
@@ -0,0 +1,1408 @@
+/* 
+ *
+ * regexp.c - regular expression matching
+ *
+ * DESCRIPTION
+ *
+ *	Underneath the reformatting and comment blocks which were added to 
+ *	make it consistent with the rest of the code, you will find a
+ *	modified version of Henry Specer's regular expression library.
+ *	Henry's functions were modified to provide the minimal regular
+ *	expression matching, as required by P1003.  Henry's code was
+ *	copyrighted, and copy of the copyright message and restrictions
+ *	are provided, verbatim, below:
+ *
+ *	Copyright (c) 1986 by University of Toronto.
+ *	Written by Henry Spencer.  Not derived from licensed software.
+ *
+ *	Permission is granted to anyone to use this software for any
+ *	purpose on any computer system, and to redistribute it freely,
+ *	subject to the following restrictions:
+ *
+ *	1. The author is not responsible for the consequences of use of
+ *         this software, no matter how awful, even if they arise
+ *	   from defects in it.
+ *
+ *	2. The origin of this software must not be misrepresented, either
+ *	   by explicit claim or by omission.
+ *
+ *	3. Altered versions must be plainly marked as such, and must not
+ *	   be misrepresented as being the original software.
+ *
+ *
+ * This version modified by Ian Phillipps to return pointer to terminating
+ * NUL on substitution string. [ Temp mail address ex-igp@camcon.co.uk ]
+ *
+ *	Altered by amylaar to support excompatible option and the
+ *      operators \< and >\ . ( 7.Sep. 1991 )
+ *
+ * regsub altered by amylaar to take an additional parameter specifying
+ * maximum number of bytes that can be written to the memory region
+ * pointed to by dest
+ *
+ * Also altered by Fredrik Hubinette to handle the + operator and
+ * eight-bit chars. Mars 22 1996
+ * 
+ *
+ * 	Beware that some of this code is subtly aware of the way operator
+ * 	precedence is structured in regular expressions.  Serious changes in
+ * 	regular-expression syntax might require a total rethink.
+ *
+ * AUTHORS
+ *
+ *     Mark H. Colburn, NAPS International (mark@jhereg.mn.org)
+ *     Henry Spencer, University of Torronto (henry@utzoo.edu)
+ *
+ * Sponsored by The USENIX Association for public distribution. 
+ *
+ */
+
+/* Headers */
+#include "global.h"
+#include <ctype.h>
+#include "regexp.h"
+#include "memory.h"
+#include "error.h"
+
+/*
+ * The "internal use only" fields in regexp.h are present to pass info from
+ * compile to execute that permits the execute phase to run lots faster on
+ * simple cases.  They are:
+ *
+ * regstart	char that must begin a match; '\0' if none obvious
+ * reganch	is the match anchored (at beginning-of-line only)?
+ * regmust	string (pointer into program) that match must include, or NULL
+ * regmlen	length of regmust string
+ *
+ * Regstart and reganch permit very fast decisions on suitable starting points
+ * for a match, cutting down the work a lot.  Regmust permits fast rejection
+ * of lines that cannot possibly match.  The regmust tests are costly enough
+ * that regcomp() supplies a regmust only if the r.e. contains something
+ * potentially expensive (at present, the only such thing detected is * or +
+ * at the start of the r.e., which can involve a lot of backup).  Regmlen is
+ * supplied because the test in regexec() needs it and regcomp() is computing
+ * it anyway.
+ */
+
+/*
+ * Structure for regexp "program".  This is essentially a linear encoding
+ * of a nondeterministic finite-state machine (aka syntax charts or
+ * "railroad normal form" in parsing technology).  Each node is an opcode
+ * plus a "nxt" pointer, possibly plus an operand.  "Nxt" pointers of
+ * all nodes except BRANCH implement concatenation; a "nxt" pointer with
+ * a BRANCH on both ends of it is connecting two alternatives.  (Here we
+ * have one of the subtle syntax dependencies:  an individual BRANCH (as
+ * opposed to a collection of them) is never concatenated with anything
+ * because of operator precedence.)  The operand of some types of node is
+ * a literal string; for others, it is a node leading into a sub-FSM.  In
+ * particular, the operand of a BRANCH node is the first node of the branch.
+ * (NB this is *not* a tree structure:  the tail of the branch connects
+ * to the thing following the set of BRANCHes.)  The opcodes are:
+ */
+
+/* definition	number	opnd?	meaning */
+#define	END	0		/* no	End of program. */
+#define	BOL	1		/* no	Match "" at beginning of line. */
+#define	EOL	2		/* no	Match "" at end of line. */
+#define	ANY	3		/* no	Match any one character. */
+#define	ANYOF	4		/* str	Match any character in this string. */
+#define	ANYBUT	5		/* str	Match any character not in this
+				 * string. */
+#define	BRANCH	6		/* node	Match this alternative, or the
+				 * nxt... */
+#define	BACK	7		/* no	Match "", "nxt" ptr points backward. */
+#define	EXACTLY	8		/* str	Match this string. */
+#define	NOTHING	9		/* no	Match empty string. */
+#define	STAR	10		/* node	Match this (simple) thing 0 or more
+				 * times. */
+#define WORDSTART 11		/* node matching a start of a word          */
+#define WORDEND 12		/* node matching an end of a word           */
+#define	OPEN	20		/* no	Mark this point in input as start of
+				 * #n. */
+ /* OPEN+1 is number 1, etc. */
+#define	CLOSE	30		/* no	Analogous to OPEN. */
+
+/*
+ * Opcode notes:
+ *
+ * BRANCH	The set of branches constituting a single choice are hooked
+ *		together with their "nxt" pointers, since precedence prevents
+ *		anything being concatenated to any individual branch.  The
+ *		"nxt" pointer of the last BRANCH in a choice points to the
+ *		thing following the whole choice.  This is also where the
+ *		final "nxt" pointer of each individual branch points; each
+ *		branch starts with the operand node of a BRANCH node.
+ *
+ * BACK		Normal "nxt" pointers all implicitly point forward; BACK
+ *		exists to make loop structures possible.
+ *
+ * STAR		complex '*', are implemented as circular BRANCH structures 
+ *		using BACK.  Simple cases (one character per match) are 
+ *		implemented with STAR for speed and to minimize recursive 
+ *		plunges.
+ *
+ * OPEN,CLOSE	...are numbered at compile time.
+ */
+
+/*
+ * A node is one char of opcode followed by two chars of "nxt" pointer.
+ * "Nxt" pointers are stored as two 8-bit pieces, high order first.  The
+ * value is a positive offset from the opcode of the node containing it.
+ * An operand, if any, simply follows the node.  (Note that much of the
+ * code generation knows about this implicit relationship.)
+ *
+ * Using two bytes for the "nxt" pointer is vast overkill for most things,
+ * but allows patterns to get big without disasters.
+ */
+#define	OP(p)	(*(p))
+#define	NEXT(p)	(((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
+#define	OPERAND(p)	((p) + 3)
+
+/*
+ * The first byte of the regexp internal "program" is actually this magic
+ * number; the start node begins in the second byte.
+ */
+#define	MAGIC	0234
+
+/*
+ * Utility definitions.
+ */
+
+#define regerror(X) error("Regexp: %s\n",X);
+#define SPECIAL 0x100
+#define LBRAC	('('|SPECIAL)
+#define RBRAC	(')'|SPECIAL)
+#define ASTERIX	('*'|SPECIAL)
+#define PLUS	('+'|SPECIAL)
+#define OR_OP	('|'|SPECIAL)
+#define DOLLAR	('$'|SPECIAL)
+#define DOT	('.'|SPECIAL)
+#define CARET	('^'|SPECIAL)
+#define LSQBRAC ('['|SPECIAL)
+#define RSQBRAC (']'|SPECIAL)
+#define LSHBRAC ('<'|SPECIAL)
+#define RSHBRAC ('>'|SPECIAL)
+#define	FAIL(m)	{ regerror(m); return(NULL); }
+#define	ISMULT(c)	((c) == ASTERIX || (c)==PLUS)
+#define	META	"^$.[()|*+\\"
+#ifndef CHARBITS
+#define CHARBITS	0xff
+#define	UCHARAT(p)	((int)*(unsigned char *)(p))
+#else
+#define	UCHARAT(p)	((int)*(p)&CHARBITS)
+#endif
+#define ISWORDPART(c) ( isalnum(c) || (c) == '_' )
+
+/*
+ * Flags to be passed up and down.
+ */
+#define	HASWIDTH	01	/* Known never to match null string. */
+#define	SIMPLE		02	/* Simple enough to be STAR operand. */
+#define	SPSTART		04	/* Starts with * */
+#define	WORST		0	/* Worst case. */
+
+/*
+ * Global work variables for regcomp().
+ */
+static short   *regparse;	/* Input-scan pointer. */
+static int      regnpar;	/* () count. */
+static char     regdummy;
+static char    *regcode;	/* Code-emit pointer; &regdummy = don't. */
+static long     regsize;	/* Code size. */
+
+/*
+ * Forward declarations for regcomp()'s friends.
+ */
+#ifndef STATIC
+#define	STATIC	static
+#endif
+STATIC char    *reg();
+STATIC char    *regbranch();
+STATIC char    *regpiece();
+STATIC char    *regatom();
+STATIC char    *regnode();
+STATIC char    *regnext();
+STATIC void     regc();
+STATIC void     reginsert();
+STATIC void     regtail();
+STATIC void     regoptail();
+
+/*
+ - regcomp - compile a regular expression into internal code
+ *
+ * We can't allocate space until we know how big the compiled form will be,
+ * but we can't compile it (and thus know how big it is) until we've got a
+ * place to put the code.  So we cheat:  we compile it twice, once with code
+ * generation turned off and size counting turned on, and once "for real".
+ * This also means that we don't allocate space until we are sure that the
+ * thing really will compile successfully, and we never have to move the
+ * code and thus invalidate pointers into it.  (Note that it has to be in
+ * one piece because free() must be able to free it all.)
+ *
+ * Beware that the optimization-preparation code in here knows about some
+ * of the structure of the compiled regexp.
+ */
+regexp *regcomp(exp,excompat)
+char   *exp;
+int		excompat;	/* \( \) operators like in unix ex */
+{
+    register regexp *r;
+    register char  *scan;
+    register char  *longest;
+    register int    len;
+    int             flags;
+    short	   *exp2,*dest,c;
+
+    if (exp == (char *)NULL)
+	FAIL("NULL argument");
+
+    exp2=(short*)xalloc( (strlen(exp)+1) * (sizeof(short[8])/sizeof(char[8])) );
+    for ( scan=exp,dest=exp2;( c= UCHARAT(scan++)); ) {
+	switch (c) {
+	    case '(':
+	    case ')':
+		*dest++ = excompat ? c : c | SPECIAL;
+		break;
+	    case '.':
+	    case '*':
+	    case '+':
+	    case '|':
+	    case '$':
+	    case '^':
+	    case '[':
+	    case ']':
+		*dest++ =  c | SPECIAL;
+		break;
+	    case '\\':
+		switch ( c = *scan++ ) {
+		    case '(':
+		    case ')':
+			*dest++ = excompat ? c | SPECIAL : c;
+			break;
+		    case '<':
+		    case '>':
+			*dest++ = c | SPECIAL;
+			break;
+		    case '{':
+		    case '}':
+			FAIL("sorry, unimplemented operator");
+		    case 'b': *dest++ = '\b'; break;
+		    case 't': *dest++ = '\t'; break;
+		    case 'r': *dest++ = '\r'; break;
+		    default:
+			*dest++ = c;
+		}
+		break;
+	    default:
+		*dest++ = c;
+	}
+    }
+    *dest=0;
+    /* First pass: determine size, legality. */
+    regparse = exp2;
+    regnpar = 1;
+    regsize = 0L;
+    regcode = &regdummy;
+    regc(MAGIC);
+    if (reg(0, &flags) == (char *)NULL)
+	return ((regexp *)NULL);
+
+    /* Small enough for pointer-storage convention? */
+    if (regsize >= 32767L)	/* Probably could be 65535L. */
+	FAIL("regexp too big");
+
+    /* Allocate space. */
+    r = (regexp *) xalloc(sizeof(regexp) + (unsigned) regsize);
+    if (r == (regexp *) NULL)
+	FAIL("out of space");
+
+    /* Second pass: emit code. */
+    regparse = exp2;
+    regnpar = 1;
+    regcode = r->program;
+    regc(MAGIC);
+    if (reg(0, &flags) == NULL)
+	return ((regexp *) NULL);
+
+    /* Dig out information for optimizations. */
+    r->regstart = '\0';		/* Worst-case defaults. */
+    r->reganch = 0;
+    r->regmust = NULL;
+    r->regmlen = 0;
+    scan = r->program + 1;	/* First BRANCH. */
+    if (OP(regnext(scan)) == END) {	/* Only one top-level choice. */
+	scan = OPERAND(scan);
+
+	/* Starting-point info. */
+	if (OP(scan) == EXACTLY)
+	    r->regstart = *OPERAND(scan);
+	else if (OP(scan) == BOL)
+	    r->reganch++;
+
+	/*
+	 * If there's something expensive in the r.e., find the longest
+	 * literal string that must appear and make it the regmust.  Resolve
+	 * ties in favor of later strings, since the regstart check works
+	 * with the beginning of the r.e. and avoiding duplication
+	 * strengthens checking.  Not a strong reason, but sufficient in the
+	 * absence of others. 
+	 */
+	if (flags & SPSTART) {
+	    longest = NULL;
+	    len = 0;
+	    for (; scan != NULL; scan = regnext(scan))
+		if (OP(scan) == EXACTLY &&
+		    (int)strlen(OPERAND(scan)) >= len) {
+		    longest = OPERAND(scan);
+		    len = strlen(OPERAND(scan));
+		}
+	    r->regmust = longest;
+	    r->regmlen = len;
+	}
+    }
+    free((char*)exp2);
+    return (r);
+}
+
+/*
+ - reg - regular expression, i.e. main body or parenthesized thing
+ *
+ * Caller must absorb opening parenthesis.
+ *
+ * Combining parenthesis handling with the base level of regular expression
+ * is a trifle forced, but the need to tie the tails of the branches to what
+ * follows makes it hard to avoid.
+ */
+static char *reg(paren, flagp)
+int             paren;		/* Parenthesized? */
+int            *flagp;
+{
+    register char  *ret;
+    register char  *br;
+    register char  *ender;
+    register int    parno=0; /* make gcc happy */
+    int             flags;
+
+    *flagp = HASWIDTH;		/* Tentatively. */
+
+    /* Make an OPEN node, if parenthesized. */
+    if (paren) {
+	if (regnpar >= NSUBEXP)
+	    FAIL("too many ()");
+	parno = regnpar;
+	regnpar++;
+	ret = regnode(OPEN + parno);
+    } else
+	ret = (char *)NULL;
+
+    /* Pick up the branches, linking them together. */
+    br = regbranch(&flags);
+    if (br == (char *)NULL)
+	return ((char *)NULL);
+    if (ret != (char *)NULL)
+	regtail(ret, br);	/* OPEN -> first. */
+    else
+	ret = br;
+    if (!(flags & HASWIDTH))
+	*flagp &= ~HASWIDTH;
+    *flagp |= flags & SPSTART;
+    while (*regparse == OR_OP) {
+	regparse++;
+	br = regbranch(&flags);
+	if (br == (char *)NULL)
+	    return ((char *)NULL);
+	regtail(ret, br);	/* BRANCH -> BRANCH. */
+	if (!(flags & HASWIDTH))
+	    *flagp &= ~HASWIDTH;
+	*flagp |= flags & SPSTART;
+    }
+
+    /* Make a closing node, and hook it on the end. */
+    ender = regnode((paren) ? CLOSE + parno : END);
+    regtail(ret, ender);
+
+    /* Hook the tails of the branches to the closing node. */
+    for (br = ret; br != (char *)NULL; br = regnext(br))
+	regoptail(br, ender);
+
+    /* Check for proper termination. */
+    if (paren && *regparse++ != RBRAC) {
+	FAIL("unmatched ()");
+    } else if (!paren && *regparse != '\0') {
+	if (*regparse == RBRAC) {
+	    FAIL("unmatched ()");
+	} else
+	    FAIL("junk on end");/* "Can't happen". */
+	/* NOTREACHED */
+    }
+    return (ret);
+}
+
+/*
+ - regbranch - one alternative of an | operator
+ *
+ * Implements the concatenation operator.
+ */
+static char  *regbranch(flagp)
+int            *flagp;
+{
+    register char  *ret;
+    register char  *chain;
+    register char  *latest;
+    int             flags;
+
+    *flagp = WORST;		/* Tentatively. */
+
+    ret = regnode(BRANCH);
+    chain = (char *)NULL;
+    while (*regparse != '\0' && *regparse != OR_OP && *regparse != RBRAC) {
+	latest = regpiece(&flags);
+	if (latest == (char *)NULL)
+	    return ((char *)NULL);
+	*flagp |= flags & HASWIDTH;
+	if (chain == (char *)NULL)	/* First piece. */
+	    *flagp |= flags & SPSTART;
+	else
+	    regtail(chain, latest);
+	chain = latest;
+    }
+    if (chain == (char *)NULL)		/* Loop ran zero times. */
+	regnode(NOTHING);
+
+    return (ret);
+}
+
+/*
+ - regpiece - something followed by possible [*]
+ *
+ * Note that the branching code sequence used for * is somewhat optimized:  
+ * they use the same NOTHING node as both the endmarker for their branch 
+ * list and the body of the last branch.  It might seem that this node could 
+ * be dispensed with entirely, but the endmarker role is not redundant.
+ */
+static char *regpiece(flagp)
+int            *flagp;
+{
+  register char  *ret;
+  register short  op;
+  /* register char  *nxt; */
+  int             flags;
+
+  ret = regatom(&flags);
+  if (ret == (char *)NULL)
+    return ((char *)NULL);
+
+  op = *regparse;
+  if (!ISMULT(op)) {
+    *flagp = flags;
+    return (ret);
+  }
+  if (!(flags & HASWIDTH))
+    FAIL("* or + operand could be empty");
+  *flagp = (WORST | SPSTART);
+
+  if(op == ASTERIX)
+  {
+    if (flags & SIMPLE)
+    {
+      reginsert(STAR, ret);
+    }
+    else
+    {
+      /* Emit x* as (x&|), where & means "self". */
+      reginsert(BRANCH, ret);	/* Either x */
+      regoptail(ret, regnode(BACK)); /* and loop */
+      regoptail(ret, ret);	/* back */
+      regtail(ret, regnode(BRANCH)); /* or */
+      regtail(ret, regnode(NOTHING)); /* null. */
+    } 
+  }
+  else if(op == PLUS)
+  {
+    /*  Emit a+ as (a&) where & means "self" /Fredrik Hubinette */
+    char *tmp;
+    tmp=regnode(BACK);
+    reginsert(BRANCH, tmp);
+    regtail(ret, tmp);
+    regoptail(tmp, ret);
+    regtail(ret, regnode(BRANCH));
+    regtail(ret, regnode(NOTHING));
+  }
+    
+  regparse++;
+  if (ISMULT(*regparse))
+    FAIL("nested * or +");
+    
+  return (ret);
+}
+
+
+/*
+ - regatom - the lowest level
+ *
+ * Optimization:  gobbles an entire sequence of ordinary characters so that
+ * it can turn them into a single node, which is smaller to store and
+ * faster to run.
+ */
+static char *regatom(flagp)
+int            *flagp;
+{
+    register char  *ret;
+    int             flags;
+
+    *flagp = WORST;		/* Tentatively. */
+
+    switch (*regparse++) {
+    case CARET:
+	ret = regnode(BOL);
+	break;
+    case DOLLAR:
+	ret = regnode(EOL);
+	break;
+    case DOT:
+	ret = regnode(ANY);
+	*flagp |= HASWIDTH | SIMPLE;
+	break;
+    case LSHBRAC:
+	ret = regnode(WORDSTART);
+	break;
+    case RSHBRAC:
+	ret = regnode(WORDEND);
+	break;
+    case LSQBRAC:{
+	    register int    class;
+	    register int    classend;
+
+	    if (*regparse == CARET) {	/* Complement of range. */
+		ret = regnode(ANYBUT);
+		regparse++;
+	    } else
+		ret = regnode(ANYOF);
+	    if (*regparse == RSQBRAC || *regparse == '-')
+		regc(*regparse++);
+	    while (*regparse != '\0' && *regparse != RSQBRAC) {
+		if (*regparse == '-') {
+		    regparse++;
+		    if (*regparse == RSQBRAC || *regparse == '\0')
+			regc('-');
+		    else {
+			class = (CHARBITS & *(regparse - 2)) + 1;
+			classend = (CHARBITS & *(regparse));
+			if (class > classend + 1)
+			    FAIL("invalid [] range");
+			for (; class <= classend; class++)
+			    regc(class);
+			regparse++;
+		    }
+		} else
+		    regc(*regparse++);
+	    }
+	    regc('\0');
+	    if (*regparse != RSQBRAC)
+		FAIL("unmatched []");
+	    regparse++;
+	    *flagp |= HASWIDTH | SIMPLE;
+	}
+	break;
+    case LBRAC:
+	ret = reg(1, &flags);
+	if (ret == (char *)NULL)
+	    return ((char *)NULL);
+	*flagp |= flags & (HASWIDTH | SPSTART);
+	break;
+    case '\0':
+    case OR_OP:
+    case RBRAC:
+	FAIL("internal urp");	/* Supposed to be caught earlier. */
+
+    case ASTERIX:
+	FAIL("* follows nothing\n");
+
+    default:{
+	    register int    len;
+	    register short  ender;
+
+	    regparse--;
+	    for (len=0; regparse[len] &&
+	        !(regparse[len]&SPECIAL) && regparse[len] != RSQBRAC; len++) ;
+	    if (len <= 0)
+	    {
+	      FAIL("internal disaster");
+	    }
+	    ender = *(regparse + len);
+	    if (len > 1 && ISMULT(ender))
+		len--;		/* Back off clear of * operand. */
+	    *flagp |= HASWIDTH;
+	    if (len == 1)
+		*flagp |= SIMPLE;
+	    ret = regnode(EXACTLY);
+	    while (len > 0) {
+		regc(*regparse++);
+		len--;
+	    }
+	    regc('\0');
+	}
+	break;
+    }
+
+    return (ret);
+}
+
+/*
+ - regnode - emit a node
+ */
+static char *regnode(op)
+char            op;
+{
+    register char  *ret;
+    register char  *ptr;
+
+    ret = regcode;
+    if (ret == &regdummy) {
+	regsize += 3;
+	return (ret);
+    }
+    ptr = ret;
+    *ptr++ = op;
+    *ptr++ = '\0';		/* Null "nxt" pointer. */
+    *ptr++ = '\0';
+    regcode = ptr;
+
+    return (ret);
+}
+
+/*
+ - regc - emit (if appropriate) a byte of code
+ */
+static void regc(b)
+char            b;
+{
+    if (regcode != &regdummy)
+	*regcode++ = b;
+    else
+	regsize++;
+}
+
+/*
+ - reginsert - insert an operator in front of already-emitted operand
+ *
+ * Means relocating the operand.
+ */
+static void reginsert(op, opnd)
+char            op;
+char           *opnd;
+{
+    register char  *src;
+    register char  *dst;
+    register char  *place;
+
+    if (regcode == &regdummy) {
+	regsize += 3;
+	return;
+    }
+    src = regcode;
+    regcode += 3;
+    dst = regcode;
+    while (src > opnd)
+	*--dst = *--src;
+
+    place = opnd;		/* Op node, where operand used to be. */
+    *place++ = op;
+    *place++ = '\0';
+    *place++ = '\0';
+}
+
+/*
+ - regtail - set the next-pointer at the end of a node chain
+ */
+static void regtail(p, val)
+char           *p;
+char           *val;
+{
+    register char  *scan;
+    register char  *temp;
+    register int    offset;
+
+    if (p == &regdummy)
+	return;
+
+    /* Find last node. */
+    scan = p;
+    for (;;) {
+	temp = regnext(scan);
+	if (temp == (char *)NULL)
+	    break;
+	scan = temp;
+    }
+
+    if (OP(scan) == BACK)
+	offset = scan - val;
+    else
+	offset = val - scan;
+    *(scan + 1) = (offset >> 8) & 0377;
+    *(scan + 2) = offset & 0377;
+}
+
+/*
+ - regoptail - regtail on operand of first argument; nop if operandless
+ */
+static void regoptail(p, val)
+char           *p;
+char           *val;
+{
+    /* "Operandless" and "op != BRANCH" are synonymous in practice. */
+    if (p == (char *)NULL || p == &regdummy || OP(p) != BRANCH)
+	return;
+    regtail(OPERAND(p), val);
+}
+
+/*
+ * regexec and friends
+ */
+
+/*
+ * Global work variables for regexec().
+ */
+static char    *reginput;	/* String-input pointer. */
+static char    *regbol;		/* Beginning of input, for ^ check. */
+static char   **regstartp;	/* Pointer to startp array. */
+static char   **regendp;	/* Ditto for endp. */
+
+/*
+ * Forwards.
+ */
+STATIC int      regtry();
+STATIC int      regmatch();
+STATIC int      regrepeat();
+
+#ifdef DEBUG
+int             regnarrate = 0;
+void            regdump();
+STATIC char    *regprop();
+#endif
+
+/*
+ - regexec - match a regexp against a string
+ */
+int regexec(prog, string)
+register regexp *prog;
+register char  *string;
+{
+    register char  *s;
+
+    /* Be paranoid... */
+    if (prog == (regexp *)NULL || string == (char *)NULL) {
+	regerror("NULL parameter");
+	return (0);
+    }
+    /* Check validity of program. */
+    if (UCHARAT(prog->program) != MAGIC) {
+	regerror("corrupted program");
+	return (0);
+    }
+    /* If there is a "must appear" string, look for it. */
+    if (prog->regmust != (char *)NULL) {
+	s = string;
+	while ((s = STRCHR(s, prog->regmust[0])) != (char *)NULL) {
+	    if (strncmp(s, prog->regmust, prog->regmlen) == 0)
+		break;		/* Found it. */
+	    s++;
+	}
+	if (s == (char *)NULL)		/* Not present. */
+	    return (0);
+    }
+    /* Mark beginning of line for ^ . */
+    regbol = string;
+
+    /* Simplest case:  anchored match need be tried only once. */
+    if (prog->reganch)
+	return (regtry(prog, string));
+
+    /* Messy cases:  unanchored match. */
+    s = string;
+    if (prog->regstart != '\0')
+	/* We know what char it must start with. */
+	while ((s = STRCHR(s, prog->regstart)) != (char *)NULL) {
+	    if (regtry(prog, s))
+		return (1);
+	    s++;
+	}
+    else
+	/* We don't -- general case. */
+	do {
+	    if (regtry(prog, s))
+		return (1);
+	} while (*s++ != '\0');
+
+    /* Failure. */
+    return (0);
+}
+
+/*
+ - regtry - try match at specific point
+ */
+#ifdef __STDC__
+
+static int regtry(regexp *prog, char *string)
+
+#else
+
+static int regtry(prog, string)
+regexp         *prog;
+char           *string;
+
+#endif
+{
+    register int    i;
+    register char **sp;
+    register char **ep;
+
+    reginput = string;
+    regstartp = prog->startp;
+    regendp = prog->endp;
+
+    sp = prog->startp;
+    ep = prog->endp;
+    for (i = NSUBEXP; i > 0; i--) {
+	*sp++ = (char *)NULL;
+	*ep++ = (char *)NULL;
+    }
+    if (regmatch(prog->program + 1)) {
+	prog->startp[0] = string;
+	prog->endp[0] = reginput;
+	return (1);
+    } else
+	return (0);
+}
+
+/*
+ - regmatch - main matching routine
+ *
+ * Conceptually the strategy is simple:  check to see whether the current
+ * node matches, call self recursively to see whether the rest matches,
+ * and then act accordingly.  In practice we make some effort to avoid
+ * recursion, in particular by going through "ordinary" nodes (that don't
+ * need to know whether the rest of the match failed) by a loop instead of
+ * by recursion.
+ */
+#ifdef __STDC__
+
+static int regmatch(char *prog)
+
+#else
+
+static int regmatch(prog)
+char           *prog;
+
+#endif
+{
+    register char  *scan;	/* Current node. */
+    char           *nxt;	/* nxt node. */
+
+    scan = prog;
+#ifdef DEBUG
+    if (scan != (char *)NULL && regnarrate)
+	fprintf(stderr, "%s(\n", regprop(scan));
+#endif
+    while (scan != (char *)NULL) {
+#ifdef DEBUG
+	if (regnarrate)
+	    fprintf(stderr, "%s...\n", regprop(scan));
+#endif
+	nxt = regnext(scan);
+
+	switch (OP(scan)) {
+	case BOL:
+	    if (reginput != regbol)
+		return (0);
+	    break;
+	case EOL:
+	    if (*reginput != '\0')
+		return (0);
+	    break;
+	case ANY:
+	    if (*reginput == '\0')
+		return (0);
+	    reginput++;
+	    break;
+	case WORDSTART:
+	    if (reginput == regbol)
+		break;
+	    if (*reginput == '\0' ||
+	       ISWORDPART( *(reginput-1) ) || !ISWORDPART( *reginput ) )
+		return (0);
+	    break;
+	case WORDEND:
+	    if (*reginput == '\0')
+		break;
+	    if ( reginput == regbol ||
+	       !ISWORDPART( *(reginput-1) ) || ISWORDPART( *reginput ) )
+		return (0);
+	    break;
+	case EXACTLY:{
+		register int    len;
+		register char  *opnd;
+
+		opnd = OPERAND(scan);
+		/* Inline the first character, for speed. */
+		if (*opnd != *reginput)
+		    return (0);
+		len = strlen(opnd);
+		if (len > 1 && strncmp(opnd, reginput, len) != 0)
+		    return (0);
+		reginput += len;
+	    }
+	    break;
+	case ANYOF:
+	    if (*reginput == '\0' || 
+		 STRCHR(OPERAND(scan), *reginput) == (char *)NULL)
+		return (0);
+	    reginput++;
+	    break;
+	case ANYBUT:
+	    if (*reginput == '\0' || 
+		 STRCHR(OPERAND(scan), *reginput) != (char *)NULL)
+		return (0);
+	    reginput++;
+	    break;
+	case NOTHING:
+	    break;
+	case BACK:
+	    break;
+	case OPEN + 1:
+	case OPEN + 2:
+	case OPEN + 3:
+	case OPEN + 4:
+	case OPEN + 5:
+	case OPEN + 6:
+	case OPEN + 7:
+	case OPEN + 8:
+	case OPEN + 9:{
+		register int    no;
+		register char  *save;
+
+		no = OP(scan) - OPEN;
+		save = reginput;
+
+		if (regmatch(nxt)) {
+		    /*
+		     * Don't set startp if some later invocation of the same
+		     * parentheses already has. 
+		     */
+		    if (regstartp[no] == (char *)NULL)
+			regstartp[no] = save;
+		    return (1);
+		} else
+		    return (0);
+	    }
+
+	case CLOSE + 1:
+	case CLOSE + 2:
+	case CLOSE + 3:
+	case CLOSE + 4:
+	case CLOSE + 5:
+	case CLOSE + 6:
+	case CLOSE + 7:
+	case CLOSE + 8:
+	case CLOSE + 9:{
+		register int    no;
+		register char  *save;
+
+		no = OP(scan) - CLOSE;
+		save = reginput;
+
+		if (regmatch(nxt)) {
+		    /*
+		     * Don't set endp if some later invocation of the same
+		     * parentheses already has. 
+		     */
+		    if (regendp[no] == (char *)NULL)
+			regendp[no] = save;
+		    return (1);
+		} else
+		    return (0);
+	    }
+
+	case BRANCH:{
+		register char  *save;
+
+		if (OP(nxt) != BRANCH)	/* No choice. */
+		    nxt = OPERAND(scan);	/* Avoid recursion. */
+		else {
+		    do {
+			save = reginput;
+			if (regmatch(OPERAND(scan)))
+			    return (1);
+			reginput = save;
+			scan = regnext(scan);
+		    } while (scan != (char *)NULL && OP(scan) == BRANCH);
+		    return (0);
+		    /* NOTREACHED */
+		}
+	    }
+	    break;
+	case STAR:{
+		register char   nextch;
+		register int    no;
+		register char  *save;
+		register int    minimum;
+
+		/*
+		 * Lookahead to avoid useless match attempts when we know
+		 * what character comes next. 
+		 */
+		nextch = '\0';
+		if (OP(nxt) == EXACTLY)
+		    nextch = *OPERAND(nxt);
+		minimum = (OP(scan) == STAR) ? 0 : 1;
+		save = reginput;
+		no = regrepeat(OPERAND(scan));
+		while (no >= minimum) {
+		    /* If it could work, try it. */
+		    if (nextch == '\0' || *reginput == nextch)
+			if (regmatch(nxt))
+			    return (1);
+		    /* Couldn't or didn't -- back up. */
+		    no--;
+		    reginput = save + no;
+		}
+		return (0);
+	    }
+
+	case END:
+	    return (1);		/* Success! */
+
+	default:
+	    regerror("memory corruption");
+	    return (0);
+
+	}
+
+	scan = nxt;
+    }
+
+    /*
+     * We get here only if there's trouble -- normally "case END" is the
+     * terminating point. 
+     */
+    regerror("corrupted pointers");
+    return (0);
+}
+
+/*
+ - regrepeat - repeatedly match something simple, report how many
+ */
+#ifdef __STDC__
+
+static int regrepeat(char *p)
+
+#else
+
+static int regrepeat(p)
+char           *p;
+
+#endif
+{
+    register int    count = 0;
+    register char  *scan;
+    register char  *opnd;
+
+    scan = reginput;
+    opnd = OPERAND(p);
+    switch (OP(p)) {
+    case ANY:
+	count = strlen(scan);
+	scan += count;
+	break;
+    case EXACTLY:
+	while (*opnd == *scan) {
+	    count++;
+	    scan++;
+	}
+	break;
+    case ANYOF:
+	while (*scan != '\0' && STRCHR(opnd, *scan) != (char *)NULL) {
+	    count++;
+	    scan++;
+	}
+	break;
+    case ANYBUT:
+	while (*scan != '\0' && STRCHR(opnd, *scan) == (char *)NULL) {
+	    count++;
+	    scan++;
+	}
+	break;
+    default:			/* Oh dear.  Called inappropriately. */
+	regerror("internal foulup");
+	count = 0;		/* Best compromise. */
+	break;
+    }
+    reginput = scan;
+
+    return (count);
+}
+
+
+/*
+ - regnext - dig the "nxt" pointer out of a node
+ */
+#ifdef __STDC__
+
+static char *regnext(register char *p)
+
+#else
+
+static char *regnext(p)
+register char  *p;
+
+#endif
+{
+    register int    offset;
+
+    if (p == &regdummy)
+	return ((char *)NULL);
+
+    offset = NEXT(p);
+    if (offset == 0)
+	return ((char *)NULL);
+
+    if (OP(p) == BACK)
+	return (p - offset);
+    else
+	return (p + offset);
+}
+
+#ifdef DEBUG
+
+STATIC char    *regprop();
+
+/*
+ - regdump - dump a regexp onto stdout in vaguely comprehensible form
+ */
+#ifdef __STDC__
+
+void regdump(regexp *r)
+
+#else
+
+void regdump(r)
+regexp         *r;
+
+#endif
+{
+    register char  *s;
+    register char   op = EXACTLY;	/* Arbitrary non-END op. */
+    register char  *nxt;
+
+    s = r->program + 1;
+    while (op != END) {		/* While that wasn't END last time... */
+	op = OP(s);
+	printf("%2ld%s", (long)(s - r->program), regprop(s));	/* Where, what. */
+	nxt = regnext(s);
+	if (nxt == (char *)NULL)	/* nxt ptr. */
+	    printf("(0)");
+	else
+	    printf("(%ld)", (long)( (s - r->program) + (nxt - s)));
+	s += 3;
+	if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
+	    /* Literal string, where present. */
+	    while (*s != '\0') {
+		putchar(*s);
+		s++;
+	    }
+	    s++;
+	}
+	putchar('\n');
+    }
+
+    /* Header fields of interest. */
+    if (r->regstart != '\0')
+	printf("start `%c' ", r->regstart);
+    if (r->reganch)
+	printf("anchored ");
+    if (r->regmust != (char *)NULL)
+	printf("must have \"%s\"", r->regmust);
+    printf("\n");
+}
+
+/*
+ - regprop - printable representation of opcode
+ */
+#ifdef __STDC__
+
+static char *regprop(char *op)
+
+#else
+
+static char *regprop(op)
+char           *op;
+
+#endif
+{
+    register char  *p;
+    static char     buf[50];
+
+    strcpy(buf, ":");
+
+    switch (OP(op)) {
+    case BOL:
+	p = "BOL";
+	break;
+    case EOL:
+	p = "EOL";
+	break;
+    case ANY:
+	p = "ANY";
+	break;
+    case ANYOF:
+	p = "ANYOF";
+	break;
+    case ANYBUT:
+	p = "ANYBUT";
+	break;
+    case BRANCH:
+	p = "BRANCH";
+	break;
+    case EXACTLY:
+	p = "EXACTLY";
+	break;
+    case NOTHING:
+	p = "NOTHING";
+	break;
+    case BACK:
+	p = "BACK";
+	break;
+    case END:
+	p = "END";
+	break;
+    case OPEN + 1:
+    case OPEN + 2:
+    case OPEN + 3:
+    case OPEN + 4:
+    case OPEN + 5:
+    case OPEN + 6:
+    case OPEN + 7:
+    case OPEN + 8:
+    case OPEN + 9:
+	sprintf(buf + strlen(buf), "OPEN%d", OP(op) - OPEN);
+	p = (char *)NULL;
+	break;
+    case CLOSE + 1:
+    case CLOSE + 2:
+    case CLOSE + 3:
+    case CLOSE + 4:
+    case CLOSE + 5:
+    case CLOSE + 6:
+    case CLOSE + 7:
+    case CLOSE + 8:
+    case CLOSE + 9:
+	sprintf(buf + strlen(buf), "CLOSE%d", OP(op) - CLOSE);
+	p = (char *)NULL;
+	break;
+    case STAR:
+	p = "STAR";
+	break;
+    default:
+	regerror("corrupted opcode");
+	p=(char *)NULL;
+	break;
+    }
+    if (p != (char *)NULL)
+	strcat(buf, p);
+    return (buf);
+}
+#endif
+
+/*
+ - regsub - perform substitutions after a regexp match
+ */
+#ifdef __STDC__
+
+char *regsub(regexp *prog, char *source, char *dest, int n)
+
+#else
+
+char *regsub(prog, source, dest, n)
+regexp         *prog;
+char           *source;
+char           *dest;
+int		n;
+
+#endif
+{
+    register char  *src;
+    register char  *dst;
+    register char   c;
+    register int    no;
+    register int    len;
+#ifndef strncpy
+    extern char    *strncpy();
+#endif
+
+    if (prog == (regexp *)NULL || 
+	source == (char *)NULL || dest == (char *)NULL) {
+	regerror("NULL parm to regsub");
+	return NULL;
+    }
+    if (UCHARAT(prog->program) != MAGIC) {
+	regerror("damaged regexp fed to regsub");
+	return NULL;
+    }
+    src = source;
+    dst = dest;
+    while ((c = *src++) != '\0') {
+	if (c == '&')
+	    no = 0;
+	else if (c == '\\' && '0' <= *src && *src <= '9')
+	    no = *src++ - '0';
+	else
+	    no = -1;
+
+	if (no < 0) {		/* Ordinary character. */
+	    if (c == '\\' && (*src == '\\' || *src == '&'))
+		c = *src++;
+	    if (--n < 0) {				/* amylaar */
+		regerror("line too long");
+		return NULL;
+	    }
+	    *dst++ = c;
+	} else if (prog->startp[no] != (char *)NULL && 
+		   prog->endp[no] != (char *)NULL) {
+	    len = prog->endp[no] - prog->startp[no];
+	    if ( (n-=len) < 0 ) {		/* amylaar */
+		regerror("line too long");
+		return NULL;
+	    }
+	    strncpy(dst, prog->startp[no], len);
+	    dst += len;
+	    if (len != 0 && *(dst - 1) == '\0') {	/* strncpy hit NUL. */
+		regerror("damaged match string");
+		return NULL;
+	    }
+	}
+    }
+    if (--n < 0) {			/* amylaar */
+    	regerror("line too long");
+    	return NULL;
+    }
+    *dst = '\0';
+    return dst;
+}
+
+
+#if 0	/* Use the local regerror() in ed.c */
+#ifdef __STDC__
+
+void regerror(char *s)
+
+#else
+
+void regerror(s)
+char           *s;
+
+#endif
+{
+    fprintf(stderr, "regexp(3): %s", s);
+    exit(1);
+}
+#endif /* 0 */
diff --git a/src/modules/Regexp/pike_regexp.h b/src/modules/Regexp/pike_regexp.h
new file mode 100644
index 0000000000000000000000000000000000000000..5fa51439e9d3a849345c4e2039523083d5f85639
--- /dev/null
+++ b/src/modules/Regexp/pike_regexp.h
@@ -0,0 +1,33 @@
+/*\
+||| This file a part of Pike, and is copyright by Fredrik Hubinette
+||| Pike is distributed as GPL (General Public License)
+||| See the files COPYING and DISCLAIMER for more information.
+\*/
+#ifndef REGEXP_H
+#define REGEXP_H
+/*
+ * Definitions etc. for regexp(3) routines.
+ *
+ * Caveat:  this is V8 regexp(3) [actually, a reimplementation thereof],
+ * not the System V one.
+ */
+
+#define NSUBEXP  40
+typedef struct regexp
+{
+  char *startp[NSUBEXP];
+  char *endp[NSUBEXP];
+  char regstart;		/* Internal use only. */
+  char reganch;			/* Internal use only. */
+  char *regmust;		/* Internal use only. */
+  int regmlen;			/* Internal use only. */
+  char program[1];		/* Unwarranted chumminess with compiler. */
+} regexp;
+
+
+
+extern regexp *regcomp();
+extern int regexec();
+extern char *regsub();
+extern void regerror();
+#endif
diff --git a/src/modules/Regexp/testsuite.in b/src/modules/Regexp/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..f20433cf9c61b7a05deb69501b0071cf92bf0206
--- /dev/null
+++ b/src/modules/Regexp/testsuite.in
@@ -0,0 +1,34 @@
+// - Here we try the regexp module
+test_true([[programp(regexp)]])
+test_any([[object o; o=clone(regexp); destruct(o); return 1]],1)
+
+// regexp->create
+test_any([[object o; o=regexp("^.*$"); destruct(o); return 1]],1)
+
+// regexp->match
+test_eq([[regexp("^.*$")->match("")]],1)
+test_eq([[regexp(".*")->match("foo")]],1)
+test_eq([[regexp("^.*$")->match("a")]],1)
+test_eq([[regexp("^.*$")->match("-")]],1)
+test_eq([[regexp("^$")->match("")]],1)
+test_eq([[regexp("^.$")->match("a")]],1)
+test_eq([[regexp("^.$")->match("-")]],1)
+test_eq([[regexp("^[abc]$")->match("-")]],0)
+test_eq([[regexp("^[abc]$")->match("a")]],1)
+test_eq([[regexp("^[abc]$")->match("c")]],1)
+test_eq([[regexp("^[^abc]$")->match("-")]],1)
+test_eq([[regexp("^[^abc]$")->match("a")]],0)
+test_eq([[regexp("^[^abc]$")->match("c")]],0)
+test_eq([[regexp("^a*$")->match("aaaa")]],1)
+test_eq([[regexp("^(a|bb)*$")->match("aabbabb")]],1)
+test_eq([[regexp("^(a|bb)*$")->match("")]],1)
+test_eq([[regexp("^(a|bb)+$")->match("")]],0)
+test_eq([[regexp("^(a|bb)+$")->match("aaa")]],1)
+test_eq([[regexp("^(a|bb)+$")->match("bbb")]],0)
+test_eq([[regexp("^(a|bb)+$")->match("bbaabba")]],1)
+test_eq([[regexp("^a|b$")->match("a")]],1)
+test_eq([[regexp("^a|b$")->match("b")]],1)
+
+// regexp->split
+test_equal([[regexp("^(a*)[^a]*$")->split("aaabbb")]],({"aaa"}))
+
diff --git a/src/modules/Ssleay/.cvsignore b/src/modules/Ssleay/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..c0bdcf80041cb0f78811b96239afcd83ff815c06
--- /dev/null
+++ b/src/modules/Ssleay/.cvsignore
@@ -0,0 +1,8 @@
+.pure
+Makefile
+config.log
+config.status
+configure
+dependencies
+linker_options
+stamp-h
diff --git a/src/modules/Ssleay/.gitignore b/src/modules/Ssleay/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7ed24cbd425f9796f4d1a7b2a62674aa61cf4353
--- /dev/null
+++ b/src/modules/Ssleay/.gitignore
@@ -0,0 +1,8 @@
+/.pure
+/Makefile
+/config.log
+/config.status
+/configure
+/dependencies
+/linker_options
+/stamp-h
diff --git a/src/modules/Ssleay/Makefile.in b/src/modules/Ssleay/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..03ef9ccd3254f8908ade770b9461f417c89c36b6
--- /dev/null
+++ b/src/modules/Ssleay/Makefile.in
@@ -0,0 +1,9 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+MODULE_CLAGS=@DEFS@ @CPPFLAGS@
+OBJS=ssleay.o
+MODULE_LDFLAGS=@LDFLAGS@ @LIBS@
+
+@dynamic_module_makefile@
+@dependencies@
+
diff --git a/src/modules/Ssleay/configure.in b/src/modules/Ssleay/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..dedd922090f9bce519a760d803e963ca3327c34a
--- /dev/null
+++ b/src/modules/Ssleay/configure.in
@@ -0,0 +1,61 @@
+AC_INIT(ssleay.c)
+
+sinclude(../module_configure.in)
+
+AC_ARG_WITH(ssleay,  [  --without-ssleay       no support for the secure socket protocol],[],[with_ssleay=yes])
+
+if test x$with_ssleay = xyes; then
+  OLD_LDFLAGS=$LDFLAGS
+  OLD_CPPFLAGS=$CPPFLAGS
+  OLD_LIBS=$LIBS
+
+  AC_MSG_CHECKING(Checking for existance of SSLeay)
+  
+  AC_CACHE_VAL(pike_cv_ssleay_exists,
+  [
+    if test -d /usr/local/ssl ; then
+      pike_cv_ssleay_exists="yes"
+    else
+      pike_cv_ssleay_exists="no"
+    fi
+  ])
+  
+  AC_MSG_RESULT($pike_cv_ssleay_exists)
+  
+  if test x$pike_cv_ssleay_exists = xyes; then
+  
+    if test -d /usr/local/ssl/lib ; then
+      echo Added /usr/local/ssl/lib to the library search path.
+      LDFLAGS="-L/usr/local/ssl/lib ${LDFLAGS}"
+      # link with libc first, so we get the right definition
+      # of crypt()
+      LDFLAGS="-lc ${LDFLAGS}"
+    fi
+
+    if test -d /usr/local/ssl/include ; then
+      echo Added /usr/local/ssl/include to the include search path.
+      CPPFLAGS="-I/usr/local/ssl/include ${CPPFLAGS}"
+    fi
+
+    pike_cv_ssleay="yes"
+
+    AC_CHECK_LIB(crypto, ERR_print_errors_fp, [], [ pike_cv_ssleay="no" ])
+    AC_CHECK_LIB(ssl, SSL_use_PrivateKey_file, [], [ pike_cv_ssleay="no" ])
+
+    AC_MSG_CHECKING(Supported version of SSLeay)
+
+    AC_MSG_RESULT($pike_cv_ssleay)
+
+    if test x$pike_cv_ssleay = xyes; then
+      AC_DEFINE(HAVE_SSLEAY)
+    else
+      # Restore variables, so we don't link with unnessesary libs
+
+      LIBS=$OLD_LIBS
+      CPPFLAGS=$OLD_CPPFLAGS
+      LDFLAGS=$OLD_LDFLAGS
+    fi
+  fi
+fi
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
diff --git a/src/modules/Ssleay/ssleay.c b/src/modules/Ssleay/ssleay.c
new file mode 100644
index 0000000000000000000000000000000000000000..5497eefe4df4ef32d2903b4e3f63dbec69f65259
--- /dev/null
+++ b/src/modules/Ssleay/ssleay.c
@@ -0,0 +1,313 @@
+/*\
+||| This file a part of Pike, and is copyright by Fredrik Hubinette
+||| Pike is distributed as GPL (General Public License)
+||| See the files COPYING and DISCLAIMER for more information.
+\*/
+
+#include "global.h"
+RCSID("$Id: ssleay.c,v 1.1 1997/02/11 08:39:19 hubbe Exp $");
+#include "types.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "stralloc.h"
+#include "array.h"
+#include "object.h"
+#include "macros.h"
+#include "backend.h"
+#include "program.h"
+#include "threads.h"
+
+#ifdef HAVE_SYS_TYPE_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SSLEAY
+
+#include <ssl.h>
+#include <crypto.h>
+#include <pem.h>
+#include <err.h>
+#include <x509.h>
+
+/* SSLeay defines _ as a macro. That's Puckat(TM). */
+#undef _
+
+struct ssleay_context
+{
+  SSL_CTX *shared;
+};
+
+struct ssleay_connection
+{
+  SSL *con;
+};
+
+
+#define THISOBJ (fp->current_object)
+#define CON (((struct ssleay_connection *) (fp->current_storage))->con)
+#define CTX (((struct ssleay_context *) (fp->current_storage))->shared)
+
+#endif /* HAVE_SSLEAY */
+
+static struct program *ssleay_program;
+static struct program *ssleay_connection_program;
+
+#ifdef HAVE_SSLEAY
+
+/* Methods for ssleay_connection objects */
+
+/* Arg is an ssleay object */
+void ssleay_connection_create(INT32 args)
+{
+  if (args < 1)
+    error("ssleay_connection->create: no context given\n");
+  if ((sp[-args].type != T_OBJECT)
+      || (sp[-args].u.object->prog != ssleay_program))
+    error("ssleay_connection->create: invalid argument\n");
+  if (CON)
+    SSL_free(CON);
+  CON = SSL_new( ( (struct ssleay_context *) sp[-args].u.object->storage)
+		 -> shared);
+  if (!CON)
+    {
+      ERR_print_errors_fp(stderr);
+      error("ssleay_connection->create: Could not allocate new connection\n");
+    }
+  SSL_clear(CON);
+}
+
+void ssleay_connection_set_fd(INT32 args)
+{
+  if ((args < 1) || (sp[-args].type != T_INT))
+    error("ssleay_connection->set_fd: wrong type\n");
+  SSL_set_fd(CON, sp[-args].u.integer);
+  pop_n_elems(args);
+}
+
+void ssleay_connection_accept(INT32 args)
+{
+  int res;
+  
+  pop_n_elems(args);
+  THREADS_ALLOW();
+  res = SSL_accept(CON);
+  THREADS_DISALLOW();
+  push_int(res);
+}
+
+void ssleay_connection_read(INT32 args)
+{
+  struct pike_string *s;
+  INT32 len;
+  INT32 count;
+  
+  if ((args < 1) || (sp[-args].type != T_INT))
+    error("ssleay_connection->read: wrong type\n");
+  len = sp[-args].u.integer;
+
+  if (len < 0)
+    error("ssleay_connection->read: invalid argument\n");
+  pop_n_elems(args);
+
+  s = begin_shared_string(len);
+  if (len)
+    {
+      THREADS_ALLOW();
+      count = SSL_read(CON, s->str, len);
+      THREADS_DISALLOW();
+      if (count < 0)
+	{
+	  free_string(end_shared_string(s));
+	  push_int(count);
+	}
+      else
+	{
+	  s->len = count;
+	  push_string(end_shared_string(s));
+	}
+    }
+}
+
+void ssleay_connection_write(INT32 args)
+{
+  INT32 res;
+
+  if ((args < 1) || (sp[-args].type != T_STRING))
+    error("ssleay_connection->write: wrong argument\n");
+  THREADS_ALLOW();
+  res = SSL_write(CON, sp[-args].u.string->str, sp[-args].u.string->len);
+  THREADS_DISALLOW();
+  pop_n_elems(args);
+  push_int(res);
+}
+
+void ssleay_connection_werror(INT32 args)
+{
+  ERR_print_errors_fp(stderr);
+  pop_n_elems(args);
+}
+
+/* Methods for ssleay context objects */
+
+static void ssleay_create(INT32 args)
+{
+  if (CTX)
+    SSL_CTX_free(CTX);
+  CTX = SSL_CTX_new();
+  if (!CTX)
+    error("ssleay->create: couldn't allocate new ssl context\n");
+  pop_n_elems(args);
+}
+
+static void ssleay_use_certificate_file(INT32 args)
+{
+  if (sp[-args].type != T_STRING)
+    error("ssleay->use_certificate_file: wrong type");
+  if (SSL_CTX_use_certificate_file(CTX, sp[-args].u.string->str, SSL_FILETYPE_PEM) <= 0)
+    {
+      ERR_print_errors_fp(stderr);
+      error("ssleay->use_certificate_file: unable to use certificate");
+    }
+  pop_n_elems(args);
+}
+
+static void ssleay_use_private_key_file(INT32 args)
+{
+  if (sp[-args].type != T_STRING)
+    error("ssleay->use_private_key_file: wrong type");
+  if (SSL_CTX_use_PrivateKey_file(CTX, sp[-args].u.string->str, SSL_FILETYPE_PEM) <= 0)
+    {
+      ERR_print_errors_fp(stderr);
+      error("ssleay->use_private_key_file: unable to use private_key\n");
+    }
+  pop_n_elems(args);
+}
+
+/* open(int fd, string mode) where mode is "s" for server
+ *  or "c" for client */
+static void ssleay_new(INT32 args)
+{
+  struct object *res;
+  
+#if 0
+  if (strcmp(sp[-args+1].u.string->str, "s") == 0)
+    is_server = 1;
+  else
+    {
+      if (strcmp(sp[-args+1].u.string->str, "c") == 0)
+	error("ssleay->open: client mode not implemented\n")
+      else
+	error("ssleay->open: invalid mode\n");
+    }
+#endif
+  pop_n_elems(args);
+  THISOBJ->refs++;
+  push_object(THISOBJ);
+  push_object(clone(ssleay_connection_program, 1));
+}
+
+
+/* Thread stuff */
+#ifdef _REENTRANT
+
+static MUTEX_T ssleay_locks[CRYPTO_NUM_LOCKS];
+
+static void ssleay_locking_callback(int mode, int type, char *file, int line)
+{
+  if (mode & CRYPTO_LOCK)
+    mt_lock(ssleay_locks + type);
+  else
+    mt_unlock(ssleay_locks + type);
+}
+
+static unsigned long ssleay_thread_id(void)
+{
+  return th_self();
+}
+      
+static void ssleay_init_threads()
+{
+  int i;
+  for (i = 0; i<CRYPTO_NUM_LOCKS; i++)
+    mt_init(ssleay_locks + i);
+  CRYPTO_set_id_callback(ssleay_thread_id);
+  CRYPTO_set_locking_callback(ssleay_locking_callback);
+}
+#endif /* _REENTRANT */
+
+/* Initializing etc */
+void init_context(struct object *o)
+{
+  CTX = NULL;
+}
+
+void exit_context(struct object *o)
+{
+  if (CTX)
+    SSL_CTX_free(CTX);
+}
+
+void init_connection(struct object *o)
+{
+  CON = NULL;
+}
+
+void exit_connection(struct object *o)
+{
+  if (CON)
+    SSL_free(CON);
+}
+
+#endif /* HAVE_SSLEAY */
+
+void pike_module_exit()
+{
+#ifdef HAVE_SSLEAY
+  free_program(ssleay_connection_program);
+  free_program(ssleay_program);
+  ssleay_connection_program=0;
+  ssleay_program=0;
+#endif
+}
+
+void pike_module_init(void)
+{
+#ifdef HAVE_SSLEAY
+  ERR_load_ERR_strings();
+  ERR_load_SSL_strings();
+  ERR_load_crypto_strings();
+#ifdef _REENTRANT
+  ssleay_init_threads();
+#endif /* _REENTRANT */
+  start_new_program();
+  add_storage(sizeof(struct ssleay_context));
+
+  add_function("create", ssleay_create, "function(void:void)",0);
+  add_function("use_certificate_file", ssleay_use_certificate_file, "function(string:void)", 0);
+  add_function("use_private_key_file", ssleay_use_private_key_file, "function(string:void)", 0);
+  add_function("new", ssleay_new, "function(void:object)", 0);
+
+  set_init_callback(init_context);
+  set_exit_callback(exit_context);
+
+  ssleay_program=end_program();
+  add_program_constant("ssleay",ssleay_program);
+  
+  start_new_program();
+  add_storage(sizeof(struct ssleay_connection));
+
+  add_function("create", ssleay_connection_create, "function(object:void)",0);
+  add_function("accept", ssleay_connection_accept, "function(void:int)", 0);
+  add_function("read",ssleay_connection_read,"function(int,int|void:int|string)",0);
+  add_function("write",ssleay_connection_write,"function(string:int)",0);
+  add_function("set_fd",ssleay_connection_set_fd,"function(int:void)",0);
+  add_function("ssleay_werror", ssleay_connection_werror, "function(void:void)", 0);
+  set_init_callback(init_connection);
+  set_exit_callback(exit_connection);
+
+  ssleay_connection_program=end_program();
+  add_program_constant("connection",ssleay_program);
+#endif /* HAVE_SSLEAY */
+}
+
+
diff --git a/src/modules/call_out/call_out.c b/src/modules/call_out/call_out.c
index f1cc05f6080713e135ffd28877c901c206343894..7c02d4db4a85ec338a1e7e4fdbd53804e5062896 100644
--- a/src/modules/call_out/call_out.c
+++ b/src/modules/call_out/call_out.c
@@ -4,7 +4,7 @@
 ||| See the files COPYING and DISCLAIMER for more information.
 \*/
 #include "global.h"
-RCSID("$Id: call_out.c,v 1.6 1997/02/07 01:39:27 hubbe Exp $");
+RCSID("$Id: call_out.c,v 1.7 1997/02/11 08:39:34 hubbe Exp $");
 #include "array.h"
 #include "dynamic_buffer.h"
 #include "object.h"
@@ -32,11 +32,20 @@ struct call_out_s
 
 typedef struct call_out_s call_out;
 
-call_out **pending_calls=0;      /* pointer to first busy pointer */
 int num_pending_calls;           /* no of busy pointers in buffer */
-static call_out **call_buffer=0; /* pointer to buffer */
+static call_out *call_buffer=0;  /* pointer to buffer */
 static int call_buffer_size;     /* no of pointers in buffer */
 
+#undef CAR
+#undef CDR
+
+#define CAR(X) (((X)<<1)+1)
+#define CDR(X) (((X)<<1)+2)
+#define PARENT(X) (((X)-1)>>1)
+#define CALL(X) call_buffer[(X)]
+#define CMP(X,Y) my_timercmp(& CALL(X).tv, <, & CALL(Y).tv)
+#define SWAP(X,Y) do{ call_out _tmp=CALL(X); CALL(X)=CALL(Y); CALL(Y)=_tmp; } while(0)
+
 static void verify_call_outs()
 {
 #ifdef DEBUG
@@ -49,18 +58,15 @@ static void verify_call_outs()
   if(num_pending_calls<0 || num_pending_calls>call_buffer_size)
     fatal("Error in call out tables.\n");
 
-  if(pending_calls+num_pending_calls!=call_buffer+call_buffer_size)
-    fatal("Error in call out tables.\n");
-
   for(e=0;e<num_pending_calls;e++)
   {
     if(e)
     {
-      if(my_timercmp(&pending_calls[e-1]->tv,>,&pending_calls[e]->tv))
-	fatal("Error in call out order.\n");
+      if(CMP(e, PARENT(e)))
+	fatal("Error in call out heap.\n");
     }
     
-    if(!(v=pending_calls[e]->args))
+    if(!(v=CALL(e).args))
       fatal("No arguments to call\n");
 
     if(v->refs < 1)
@@ -72,6 +78,54 @@ static void verify_call_outs()
 #endif
 }
 
+static void adjust_down(int pos)
+{
+  while(1)
+  {
+    int a=CAR(pos), b=CDR(pos);
+    if(a >= num_pending_calls) break;
+    if(b < num_pending_calls)
+      if(CMP(b, a))
+	a=b;
+
+    if(CMP(pos, a)) break;
+    SWAP(pos, a);
+    pos=a;
+  }
+}
+
+static int adjust_up(int pos)
+{
+  int parent=PARENT(pos);
+  int from;
+  if(!pos) return 0;
+
+  if(CMP(pos, parent))
+  {
+    SWAP(pos, parent);
+    from=pos;
+    pos=parent;
+    while(pos && CMP(pos, parent=PARENT(pos)))
+    {
+      SWAP(pos, parent);
+      from=pos;
+      pos=parent;
+    }
+    from^=1;
+    if(from < num_pending_calls && CMP(from, pos))
+    {
+      SWAP(from, pos);
+      adjust_down(from);
+    }
+    return 1;
+  }
+  return 0;
+}
+
+static void adjust(int pos)
+{
+  if(!adjust_up(pos)) adjust_down(pos);
+}
 
 /* start a new call out, return 1 for success */
 static struct array * new_call_out(int num_arg,struct svalue *argp)
@@ -79,40 +133,31 @@ static struct array * new_call_out(int num_arg,struct svalue *argp)
   int e,c;
   call_out *new,**p,**pos;
 
-  if(!call_buffer)
-  {
-    call_buffer_size=20;
-    call_buffer=(call_out **)xalloc(sizeof(call_out *)*call_buffer_size);
-    if(!call_buffer) return 0;
-    pending_calls=call_buffer+call_buffer_size;
-    num_pending_calls=0;
-  }
-
   if(num_pending_calls==call_buffer_size)
   {
     /* here we need to allocate space for more pointers */
-    call_out **new_buffer;
-
-    new_buffer=(call_out **)xalloc(sizeof(call_out *)*call_buffer_size*2);
-    if(!new_buffer)
-      return 0;
-
-    MEMCPY((char *)(new_buffer+call_buffer_size),
-	   (char *)call_buffer,
-	   sizeof(call_out *)*call_buffer_size);
-    free((char *)call_buffer);
-    call_buffer=new_buffer;
-    pending_calls=call_buffer+call_buffer_size;
-    call_buffer_size*=2;
+    call_out *new_buffer;
+
+    if(!call_buffer)
+    {
+      call_buffer_size=128;
+      call_buffer=(call_out *)xalloc(sizeof(call_out)*call_buffer_size);
+      if(!call_buffer) return 0;
+      num_pending_calls=0;
+    }else{
+      new_buffer=(call_out *)realloc((char *)call_buffer, sizeof(call_out)*call_buffer_size*2);
+      if(!new_buffer)
+	error("Not enough memorry for another call_out\n");
+      call_buffer_size*=2;
+      call_buffer=new_buffer;
+    }
   }
 
   /* time to allocate a new call_out struct */
   f_aggregate(num_arg-1);
 
-  new=(call_out *)xalloc(sizeof(call_out));
+  new=&CALL(num_pending_calls);
 
-  if(!new) return 0;
-  
   if(argp[0].type==T_INT)
   {
     new->tv.tv_sec=argp[0].u.integer;
@@ -142,29 +187,9 @@ static struct array * new_call_out(int num_arg,struct svalue *argp)
   new->args=sp[-1].u.array;
   sp -= 2;
 
-  /* time to link it into the buffer using binsearch */
-  pos=pending_calls;
-
-  e=num_pending_calls;
-  while(e>0)
-  {
-    c=e/2;
-    if(my_timercmp(& new->tv,>,& pos[c]->tv))
-    {
-      pos+=c+1;
-      e-=c+1;
-    }else{
-      e=c;
-    }
-  }
-  pos--;
-  pending_calls--;
-  for(p=pending_calls;p<pos;p++) p[0]=p[1];
-  *pos=new;
   num_pending_calls++;
-
+  adjust_up(num_pending_calls-1);
   verify_call_outs();
-
   return new->args;
 }
 
@@ -223,18 +248,18 @@ void do_call_outs(struct callback *ignored, void *ignored_too, void *arg)
   {
     tmp=(time_t)TIME(0);
     while(num_pending_calls &&
-	  my_timercmp(&pending_calls[0]->tv,<=,&current_time))
+	  my_timercmp(&CALL(0).tv,<=,&current_time))
     {
       /* unlink call out */
-      c=pending_calls[0];
-      pending_calls++;
-      num_pending_calls--;
+      call_out c;
+      c=CALL(0);
+      CALL(0)=CALL(--num_pending_calls);
+      adjust_down(0);
 
-      if(c->caller) free_object(c->caller);
+      if(c.caller) free_object(c.caller);
 
-      args=c->args->size;
-      push_array_items(c->args);
-      free((char *)c);
+      args=c.args->size;
+      push_array_items(c.args);
       check_destructed(sp-args);
       if(sp[-args].type!=T_INT)
       {
@@ -252,15 +277,15 @@ void do_call_outs(struct callback *ignored, void *ignored_too, void *arg)
   {
     if(num_pending_calls)
     {
-      if(my_timercmp(& pending_calls[0]->tv, < , &next_timeout))
+      if(my_timercmp(& CALL(0).tv, < , &next_timeout))
       {
-	next_timeout = pending_calls[0]->tv;
+	next_timeout = CALL(0).tv;
       }
     }else{
       if(call_out_backend_callback)
       {
 	/* There are no call outs queued, let's remove
-	 * the taxing backend callback...
+	 * the "taxing" backend callback...
 	 */
 	remove_callback(call_out_backend_callback);
 	call_out_backend_callback=0;
@@ -275,11 +300,11 @@ static int find_call_out(struct svalue *fun)
 
   if(fun->type == T_ARRAY)
     for(e=0;e<num_pending_calls;e++)
-      if(pending_calls[e]->args == fun->u.array)
+      if(CALL(e).args == fun->u.array)
 	return e;
 
   for(e=0;e<num_pending_calls;e++)
-    if(is_eq(fun, ITEM(pending_calls[e]->args)))
+    if(is_eq(fun, ITEM(CALL(e).args)))
       return e;
 
   return -1;
@@ -298,7 +323,7 @@ void f_find_call_out(INT32 args)
     sp->u.integer=-1;
     sp++;
   }else{
-    push_int(pending_calls[e]->tv.tv_sec - current_time.tv_sec);
+    push_int(CALL(e).tv.tv_sec - current_time.tv_sec);
   }
   verify_call_outs();
 }
@@ -311,15 +336,12 @@ void f_remove_call_out(INT32 args)
   if(e!=-1)
   {
     pop_n_elems(args);
-    push_int(pending_calls[e]->tv.tv_sec - current_time.tv_sec);
-    free_array(pending_calls[e]->args);
-    if(pending_calls[e]->caller)
-      free_object(pending_calls[e]->caller);
-    free((char*)(pending_calls[e]));
-    for(;e>0;e--)
-      pending_calls[e]=pending_calls[e-1];
-    pending_calls++;
-    num_pending_calls--;
+    push_int(CALL(e).tv.tv_sec - current_time.tv_sec);
+    free_array(CALL(e).args);
+    if(CALL(e).caller)
+      free_object(CALL(e).caller);
+    CALL(e)=CALL(--num_pending_calls);
+    adjust(e);
   }else{
     pop_n_elems(args);
     sp->type=T_INT;
@@ -343,22 +365,23 @@ struct array *get_all_call_outs()
   for(e=0;e<num_pending_calls;e++)
   {
     struct array *v;
-    v=allocate_array_no_init(pending_calls[e]->args->size+2, 0);
+    v=allocate_array_no_init(CALL(e).args->size+2, 0);
     ITEM(v)[0].type=T_INT;
     ITEM(v)[0].subtype=NUMBER_NUMBER;
-    ITEM(v)[0].u.integer=pending_calls[e]->tv.tv_sec - current_time.tv_sec;
+    ITEM(v)[0].u.integer=CALL(e).tv.tv_sec - current_time.tv_sec;
 
-    if(pending_calls[e]->caller)
+    if(CALL(e).caller)
     {
       ITEM(v)[1].type=T_OBJECT;
-      (ITEM(v)[1].u.object=pending_calls[e]->caller) ->refs++;
+      ITEM(v)[1].u.object=CALL(e).caller;
+      CALL(e).caller->refs++;
     }else{
       ITEM(v)[1].type=T_INT;
       ITEM(v)[1].subtype=NUMBER_NUMBER;
       ITEM(v)[1].u.integer=0;
     }
 
-    assign_svalues_no_free(ITEM(v)+2,ITEM(pending_calls[e]->args),pending_calls[e]->args->size,BIT_MIXED);
+    assign_svalues_no_free(ITEM(v)+2,ITEM(CALL(e).args),CALL(e).args->size,BIT_MIXED);
 
     ITEM(ret)[e].type=T_ARRAY;
     ITEM(ret)[e].u.array=v;
@@ -378,14 +401,12 @@ void free_all_call_outs()
   verify_call_outs();
   for(e=0;e<num_pending_calls;e++)
   {
-    free_array(pending_calls[e]->args);
-    if(pending_calls[e]->caller) free_object(pending_calls[e]->caller);
-    free((char*)(pending_calls[e]));
+    free_array(CALL(e).args);
+    if(CALL(e).caller) free_object(CALL(e).caller);
   }
   if(call_buffer) free((char*)call_buffer);
   num_pending_calls=0;
   call_buffer=NULL;
-  pending_calls=NULL;
 }
 
 #ifdef DEBUG
diff --git a/src/modules/files/testsuite.in b/src/modules/files/testsuite.in
index 168cdeead3617b84b14717ef0f220dee82310cd5..70959e39ae89ca1a1f1caa544daaf9fd2e8e87db 100644
--- a/src/modules/files/testsuite.in
+++ b/src/modules/files/testsuite.in
@@ -1,12 +1,12 @@
 // tests for file module
-test_true(programp((program)"/precompiled/file"))
-test_true(programp(File))
-test_true(programp((program)"/precompiled/port"))
-test_any(object o; o=clone(File); destruct(o); return 1,1)
+test_true(programp(Stdio.File))
+test_true(programp(Stdio.File))
+test_true(programp(Stdio.Port))
+test_any(object o; o=clone(Stdio.File); destruct(o); return 1,1)
 
 // - file->open
 // - file->close
-test_any(object o=clone(File); return o->open("conftest","wct") && o->close(),1)
+test_any(object o=clone(Stdio.File); return o->open("conftest","wct") && o->close(),1)
 
 // - file_stat
 test_eq(file_stat("conftest")[1],0)
@@ -16,59 +16,59 @@ test_true(rm("conftest"))
 test_eq(file_stat("conftest"),0)
 
 // - file->write
-test_any(int e; object o=clone(File); if(!o->open("conftest","wct")) return -1; e=o->write("sune"); if(!o->close()) return -1; return e,4)
+test_any(int e; object o=clone(Stdio.File); if(!o->open("conftest","wct")) return -1; e=o->write("sune"); if(!o->close()) return -1; return e,4)
 
 // - file->read
-test_any(string s; object o=clone(File); if(!o->open("conftest","r")) return -1; s=o->read(4); if(!o->close()) return -1; return s,"sune")
+test_any(string s; object o=clone(Stdio.File); if(!o->open("conftest","r")) return -1; s=o->read(4); if(!o->close()) return -1; return s,"sune")
 
-test_any(string s; object o=clone(File); if(!o->open("conftest","r")) return -1; s=o->read(999999); if(!o->close()) return -1; return s,"sune")
+test_any(string s; object o=clone(Stdio.File); if(!o->open("conftest","r")) return -1; s=o->read(999999); if(!o->close()) return -1; return s,"sune")
 
-test_any(int e; object o=clone(File); if(!o->open("conftest","wct")) return -1; e=o->write(sprintf("%'+-*'100000s","")); if(!o->close()) return -1; return e,100000)
+test_any(int e; object o=clone(Stdio.File); if(!o->open("conftest","wct")) return -1; e=o->write(sprintf("%'+-*'100000s","")); if(!o->close()) return -1; return e,100000)
 
-test_any(string s; object o=clone(File); if(!o->open("conftest","r")) return -1; s=o->read(9999999); if(!o->close()) return -1; return s,sprintf("%'+-*'100000s",""))
+test_any(string s; object o=clone(Stdio.File); if(!o->open("conftest","r")) return -1; s=o->read(9999999); if(!o->close()) return -1; return s,sprintf("%'+-*'100000s",""))
 
 // - file->seek
 // - file->tell
-test_any(object o=clone(File); return o->open("conftest","r") && o->read(4711) && o->tell() == 4711 && o->close(),1)
+test_any(object o=clone(Stdio.File); return o->open("conftest","r") && o->read(4711) && o->tell() == 4711 && o->close(),1)
 
 // - file->stat
-test_any(object o=clone(File); return equal(o->open("conftest","r") && o->stat(), file_stat("conftest")),1)
+test_any(object o=clone(Stdio.File); return equal(o->open("conftest","r") && o->stat(), file_stat("conftest")),1)
 
 // - file->errno
-test_do(clone(File,"stdin")->errno())
+test_do(clone(Stdio.File,"stdin")->errno())
 
 // - file->set_nonblocking
 // - file->set_blocking
 // - file->set_id
 // - file->query_id
-test_false(clone(File,"stdin")->query_id())
+test_false(clone(Stdio.File,"stdin")->query_id())
 
 // - File->query_read_callback
-test_do(clone(File,"stdin")->query_read_callback())
+test_do(clone(Stdio.File,"stdin")->query_read_callback())
 
 // - file->query_write_callback
-test_do(clone(File,"stdin")->query_write_callback())
+test_do(clone(Stdio.File,"stdin")->query_write_callback())
 
 // - file->query_close_callback
-test_do(clone(File,"stdin")->query_close_callback())
+test_do(clone(Stdio.File,"stdin")->query_close_callback())
 
 // - file->open_socket
 // - file->connect
 // - file->query_address
 // - file->pipe
-test_any([[object o=clone(File),o2=o->pipe();o->write("1"); return o2->read(1)]],"1")
-test_any([[object o=clone(File),o2=o->pipe();o2->write("1"); return o->read(1)]],"1")
+test_any([[object o=clone(Stdio.File),o2=o->pipe();o->write("1"); return o2->read(1)]],"1")
+test_any([[object o=clone(Stdio.File),o2=o->pipe();o2->write("1"); return o->read(1)]],"1")
 
 // - file->dup
-test_any([[object o=clone(File); o->open("conftest","r"); o=o->dup(); return o->read(100)]] ,sprintf("%'+-*'100s",""))
+test_any([[object o=clone(Stdio.File); o->open("conftest","r"); o=o->dup(); return o->read(100)]] ,sprintf("%'+-*'100s",""))
 
 // - file->assign
-est_any([[object o=clone(File),o2=clone(File); o->open("conftest","r"); o2->assign(o); return o2->read(100)]] ,sprintf("%'+-*'100s",""))
+est_any([[object o=clone(Stdio.File),o2=clone(Stdio.File); o->open("conftest","r"); o2->assign(o); return o2->read(100)]] ,sprintf("%'+-*'100s",""))
 
 // - file->dup2
-test_any([[object o=clone(File),o2=clone(File); o2->pipe(); o->open("conftest","r"); o->dup2(o2); return o2->read(100)]] ,sprintf("%'+-*'100s",""))
+test_any([[object o=clone(Stdio.File),o2=clone(Stdio.File); o2->pipe(); o->open("conftest","r"); o->dup2(o2); return o2->read(100)]] ,sprintf("%'+-*'100s",""))
 
-test_eq(popen("echo foo"),"foo\n")
+test_eq(Process.popen("echo foo"),"foo\n")
 
 // - socket->bind
 // - socket->set_id
@@ -92,29 +92,25 @@ test_true(stringp(getcwd()))
 test_eq('/',getcwd()[0])
 
 // strerror
-cond([[all_efuns()->strerror]],
+cond([[all_constants()->strerror]],
 [[
 test_do(strerror(1))
 test_true(stringp(strerror(2)||""))
 ]])
 
 
-test_do(object o=clone(File); if(!o->open("conftest","wct")) return -1; o->write(strmult("foo\n",100)); o->close();)
-
-test_any([[
-#include <stdio.h>
-return 0]],0)
+test_do(object o=clone(Stdio.File); if(!o->open("conftest","wct")) return -1; o->write(String.strmult("foo\n",100)); o->close();)
 
 // /precompiled/FILE
-test_any([[object o=clone(FILE); o->open("conftest","r"); return o->gets()]],"foo")
-test_any(object o=clone(FILE); o->open("conftest","r"); return o->gets()+o->gets()+o->gets(),"foofoofoo")
-test_any(int e; object o=clone(FILE); o->open("conftest","r"); for(e=0;e<100;e++) if(o->gets() != "foo") return e; return -1,-1)
+test_any([[object o=clone(Stdio.FILE); o->open("conftest","r"); return o->gets()]],"foo")
+test_any(object o=clone(Stdio.FILE); o->open("conftest","r"); return o->gets()+o->gets()+o->gets(),"foofoofoo")
+test_any(int e; object o=clone(Stdio.FILE); o->open("conftest","r"); for(e=0;e<100;e++) if(o->gets() != "foo") return e; return -1,-1)
 
-test_true(stdin)
-test_true(stdout)
-test_true(stderr)
+test_true(Stdio.stdin)
+test_true(Stdio.stdout)
+test_true(Stdio.stderr)
 
-test_eq(read_file("conftest",0,5),strmult("foo\n",5))
-test_eq(read_file("conftest",1,5),strmult("foo\n",5))
-test_eq(read_file("conftest",100,5),"")
+test_eq(Stdio.read_file("conftest",0,5),String.strmult("foo\n",5))
+test_eq(Stdio.read_file("conftest",1,5),String.strmult("foo\n",5))
+test_eq(Stdio.read_file("conftest",100,5),"")
 test_do(rm("conftest"))
\ No newline at end of file
diff --git a/src/modules/readline/.cvsignore b/src/modules/readline/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..74ee34e458820cc9a20357e972f33acb4b423f03
--- /dev/null
+++ b/src/modules/readline/.cvsignore
@@ -0,0 +1,10 @@
+.pure
+Makefile
+config.log
+config.status
+configure
+dependencies
+lib_dirs
+linker_options
+readline_machine.h
+stamp-h
diff --git a/src/modules/readline/.gitignore b/src/modules/readline/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..85b1cf5830cd9ca112606ef69527ffa8b619bbbf
--- /dev/null
+++ b/src/modules/readline/.gitignore
@@ -0,0 +1,10 @@
+/.pure
+/Makefile
+/config.log
+/config.status
+/configure
+/dependencies
+/lib_dirs
+/linker_options
+/readline_machine.h
+/stamp-h
diff --git a/src/modules/readline/Makefile.in b/src/modules/readline/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..d16c08f3ad4f3ceeb4f81f7becad5a073452eff9
--- /dev/null
+++ b/src/modules/readline/Makefile.in
@@ -0,0 +1,8 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+MODULE_CPPFLAGS=@CPPFLAGS@
+MODULE_LDFLAGS=@LDFLAGS@ @LIBS@
+OBJS=readlinemod.o
+
+@dynamic_module_makefile@
+@dependencies@
diff --git a/src/modules/readline/configure.in b/src/modules/readline/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..2d27610c9c5ff16709debbe3e480da6bd5f4ae60
--- /dev/null
+++ b/src/modules/readline/configure.in
@@ -0,0 +1,52 @@
+AC_INIT(readlinemod.c)
+AC_CONFIG_HEADER(readline_machine.h)
+AC_ARG_WITH(readline,[  --with(out)-readline   support command line editing],[],[with_readline=yes])
+
+sinclude(../module_configure.in)
+
+if test x$with_readline = xyes ; then
+  AC_MSG_CHECKING(Checking for GNU directory)
+
+  AC_CACHE_VAL(pike_cv_gnu_dir, [
+    for pike_cv_gnu_dir in /usr/gnu /opt/gnu /usr/local/gnu /sw/gnu no; do
+      if test -d $pike_cv_gnu_dir/. ; then
+	break
+      else
+	:
+      fi
+    done
+  ])
+  AC_MSG_RESULT($pike_cv_gnu_dir)
+
+  if test x$pike_cv_gnu_dir != xno; then
+    if test -d $pike_cv_gnu_dir/include/. ; then
+      echo Adding $pike_cv_gnu_dir/include to the include path
+      CPPFLAGS="$CPPFLAGS -I$pike_cv_gnu_dir/include"
+    else
+      :
+    fi
+    if test -d $pike_cv_gnu_dir/lib/. ; then
+      echo Adding $pike_cv_gnu_dir/lib to the runtime link path
+      echo $pike_cv_gnu_dir/lib >lib_dirs
+    elif test -f lib_dirs ; then
+      rm lib_dirs
+    else
+      :
+    fi
+  else
+    :
+  fi
+
+  AC_CHECK_HEADERS(readline.h history.h readline/readline.h history/history.h readline/history.h)
+
+  if test $ac_cv_header_readline_h = yes -o $ac_cv_header_readline_readline_h = yes ; then
+    if test $ac_cv_header_history_h = yes -o $ac_cv_header_history_history_h = yes -o $ac_cv_header_readline_history_h = yes ; then
+      AC_CHECK_LIB(termcap, tputs)
+      AC_CHECK_LIB(readline, readline)
+    fi
+  fi
+fi
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
+
+
diff --git a/src/modules/readline/doc/readline b/src/modules/readline/doc/readline
new file mode 100644
index 0000000000000000000000000000000000000000..01f3204c02d443f207edaa5b1348f320f1031c28
--- /dev/null
+++ b/src/modules/readline/doc/readline
@@ -0,0 +1,14 @@
+NAME
+	readline - read a line from stdin
+
+SYNTAX
+	string readline(string prompt);
+
+DESCRIPTION
+	This function writes the string 'prompt' and then waits until the
+	user has entered a line from the keyboard. If the readline library
+	was available when Pike was compiled the user will have history and
+	line edithing at his/her disposal when entering the line.
+
+SEE ALSO
+	files/file
diff --git a/src/modules/readline/readline_machine.h.in b/src/modules/readline/readline_machine.h.in
new file mode 100644
index 0000000000000000000000000000000000000000..7ce5c2ec3ae6a7ec83311f5c10fc614513a3f908
--- /dev/null
+++ b/src/modules/readline/readline_machine.h.in
@@ -0,0 +1,19 @@
+#ifndef GDBM_MACHINE_H
+#define GDBM_MACHINE_H
+
+/* Define this if you have <readline.h> */
+#undef HAVE_READLINE_H
+#undef HAVE_READLINE_READLINE_H
+
+/* Define this if you have <history.h> */
+#undef HAVE_HISTORY_H
+#undef HAVE_HISTORY_HISTORY_H
+#undef HAVE_READLINE_HISTORY_H
+
+/* Define this if you have -lreadline */
+#undef HAVE_LIBREADLINE
+
+/* Define this if you have -ltermcap */
+#undef HAVE_LIBTERMCAP
+
+#endif
diff --git a/src/modules/readline/readlinemod.c b/src/modules/readline/readlinemod.c
new file mode 100644
index 0000000000000000000000000000000000000000..ceaa4ed132e6e1300e49248d34dbc5dca28a3e83
--- /dev/null
+++ b/src/modules/readline/readlinemod.c
@@ -0,0 +1,140 @@
+/*\
+||| This file a part of Pike, and is copyright by Fredrik Hubinette
+||| Pike is distributed as GPL (General Public License)
+||| See the files COPYING and DISCLAIMER for more information.
+\*/
+#include "global.h"
+#include "readline_machine.h"
+#include "types.h"
+#include "interpret.h"
+#include "svalue.h"
+#include "stralloc.h"
+#include "array.h"
+#include "object.h"
+#include "macros.h"
+#include "threads.h"
+
+#ifndef HAVE_LIBTERMCAP
+#undef HAVE_LIBREADLINE
+#endif
+
+#if !defined(HAVE_READLINE_H) && !defined(HAVE_READLINE_READLINE_H)
+#undef HAVE_LIBREADLINE
+#endif
+
+#if !defined(HAVE_HISTORY_H) && !defined(HAVE_READLINE_HISTORY_H) && !defined(HAVE_HISTORY_HISTORY_H)
+#undef HAVE_LIBREADLINE
+#endif
+
+#ifdef HAVE_LIBREADLINE
+
+#ifdef HAVE_READLINE_READLINE_H
+#include <readline/readline.h>
+#else
+#ifdef HAVE_READLINE_H
+#include <readline.h>
+#endif
+#endif
+
+#ifdef HAVE_READLINE_HISTORY_H
+#include <readline/history.h>
+#else
+#ifdef HAVE_HISTORY_HISTORY_H
+#include <history/history.h>
+#else
+#ifdef HAVE_HISTORY_H
+#include <history.h>
+#endif
+#endif
+#endif
+
+static void f_readline(INT32 args)
+{
+  char *r;
+  struct pike_string *str;
+  if(args < 1)
+    error("Too few arguments to readline().\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to readline()\n");
+
+  str=sp[-args].u.string;
+  THREADS_ALLOW();
+  r=readline(str->str);
+  THREADS_DISALLOW();
+
+  pop_n_elems(args);
+  if(r)
+  {
+    if(*r) add_history(r);
+    push_string(make_shared_string(r));
+    free(r);
+  } else {
+    push_int(0);
+  }
+}
+
+void pike_module_init(void)
+{
+  rl_bind_key('\t', rl_insert);
+  add_function_constant("_module_value",f_readline,"function(string:string)",OPT_SIDE_EFFECT);
+}
+
+#else
+
+#include <stdio.h>
+
+#define BLOCK 16384
+
+static void f_readline(INT32 args)
+{
+  char *prompt;
+  int plen;
+  char line[BLOCK];
+  char *r;
+  int tmp;
+
+  if(args < 1)
+    error("Too few arguments to readline().\n");
+
+  if(sp[-args].type != T_STRING)
+    error("Bad argument 1 to readline()\n");
+
+  prompt = sp[-args].u.string->str;
+  plen = sp[-args].u.string->len;
+
+  THREADS_ALLOW();
+
+  write(1, prompt, plen);
+  r=fgets(line,BLOCK-1,stdin);	/* Should probably get rid of this one */
+  line[BLOCK-1] = '\0';		/* Always NUL-terminated */
+
+  THREADS_DISALLOW();
+
+  pop_n_elems(args);
+
+  if (r)
+  {
+    INT32 len;
+    if ((len=strlen(line)))
+    {
+      if (line[len-1]=='\n')
+      {
+	push_string(make_shared_binary_string(line,len-1));
+	return;
+      }
+    }
+  }
+  push_int(0);
+}
+
+void pike_module_init(void)
+{
+  add_function_constant("_module_value",f_readline,"function(string:string)",OPT_SIDE_EFFECT);
+}
+
+#endif
+
+void pike_module_exit(void) {}
+
+
diff --git a/src/modules/readline/testsuite.in b/src/modules/readline/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391