Index: trunk/psLib/test/imageops/.cvsignore
===================================================================
--- trunk/psLib/test/imageops/.cvsignore	(revision 11691)
+++ trunk/psLib/test/imageops/.cvsignore	(revision 11703)
@@ -23,2 +23,4 @@
 tap_psImageShift
 tap_psImageShiftKernel
+tap_psImageConvolve
+tap_psImageConvolve2
Index: trunk/psLib/test/imageops/Makefile.am
===================================================================
--- trunk/psLib/test/imageops/Makefile.am	(revision 11691)
+++ trunk/psLib/test/imageops/Makefile.am	(revision 11703)
@@ -21,4 +21,5 @@
 	tap_psImageStructManip \
 	tap_psImageConvolve \
+	tap_psImageConvolve2 \
 	tap_psImagePixelExtract
 
Index: trunk/psLib/test/imageops/tap_psImageConvolve2.c
===================================================================
--- trunk/psLib/test/imageops/tap_psImageConvolve2.c	(revision 11703)
+++ trunk/psLib/test/imageops/tap_psImageConvolve2.c	(revision 11703)
@@ -0,0 +1,149 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+#define IMAGE_SIZE 21
+#define KERNEL_SIZE 3
+#define TOL 1.0e-6
+
+
+// Generate image with single high pixel
+static psImage *generateImage(void)
+{
+    psImage *image = psImageAlloc(IMAGE_SIZE, IMAGE_SIZE, PS_TYPE_F32);
+    psImageInit(image, 0.0);
+    image->data.F32[IMAGE_SIZE/2][IMAGE_SIZE/2] = 1.0;
+    return image;
+}
+
+// Generate Gaussian kernel
+static psKernel *generateKernel(void)
+{
+    psKernel *kernel = psKernelAlloc(- KERNEL_SIZE, KERNEL_SIZE, - KERNEL_SIZE, KERNEL_SIZE);
+    for (int y = - KERNEL_SIZE; y <= KERNEL_SIZE; y++) {
+        for (int x = - KERNEL_SIZE; x <= KERNEL_SIZE; x++) {
+            kernel->kernel[y][x] = exp(- x*x - y*y);
+        }
+    }
+    return kernel;
+}
+
+// Check the convolved image matches the kernel
+static bool checkConvolved(const psImage *image, const psKernel *kernel)
+{
+    bool correct = true;
+    for (int y = 0; y < IMAGE_SIZE; y++) {
+        for (int x = 0; x < IMAGE_SIZE; x++) {
+            if (x < IMAGE_SIZE/2 - KERNEL_SIZE || x > IMAGE_SIZE/2 + KERNEL_SIZE ||
+                y < IMAGE_SIZE/2 - KERNEL_SIZE || y > IMAGE_SIZE/2 + KERNEL_SIZE) {
+                if (fabs(image->data.F32[y][x]) > TOL) {
+                    diag("%d,%d --> %f", x, y, fabs(image->data.F32[y][x]));
+                    correct = false;
+                }
+            } else {
+                // Position relative to the centre
+                int kx = x - IMAGE_SIZE/2;
+                int ky = y - IMAGE_SIZE/2;
+                if (fabs(image->data.F32[y][x] - kernel->kernel[ky][kx]) > TOL) {
+                    diag("%d,%d --> %f", x, y, fabs(image->data.F32[y][x] - kernel->kernel[ky][kx]));
+                    correct = false;
+                }
+            }
+        }
+    }
+    return correct;
+}
+
+
+int main(int argc, char *argv[])
+{
+    plan_tests(16);
+
+    diag("psImageConvolve tests");
+
+    {
+        // Direct convolution
+        psMemId id = psMemGetId();
+
+        psImage *image = generateImage();
+        psKernel *kernel = generateKernel();
+
+        psImage *convolved = psImageConvolveDirect(NULL, image, kernel);
+        ok(convolved, "convolution result");
+        skip_start(!convolved, 3, "convolution failed");
+        ok(convolved->type.type == PS_TYPE_F32, "output type");
+        ok(convolved->numCols == IMAGE_SIZE && convolved->numRows == IMAGE_SIZE, "output size %dx%d",
+           convolved->numCols, convolved->numRows);
+        ok(checkConvolved(convolved, kernel), "convolved image correct");
+        psFree(convolved);
+        skip_end();
+
+        psFree(kernel);
+        psFree(image);
+
+        ok(!psMemCheckLeaks(id, NULL, NULL, false), "no memory leaks");
+    }
+
+    {
+        // FFT forward, then back --- do I get what I started with?
+        psMemId id = psMemGetId();
+
+        psImage *old = generateImage();
+        psImage *fftReal = NULL, *fftImag = NULL;
+        bool result = psImageForwardFFT(&fftReal, &fftImag, old);
+        ok(result, "forward fft result");
+        skip_start(!result, 3, "forward fft failed");
+        ok(fftReal->type.type == PS_TYPE_F32 && fftImag->type.type == PS_TYPE_F32, "forward fft types");
+        psImage *new = NULL;
+        result = psImageBackwardFFT(&new, fftReal, fftImag, old->numCols);
+        ok(result, "backward fft result");
+        skip_start(!result, 2, "backward fft failed");
+        ok(new->type.type == PS_TYPE_F32, "backward fft type");
+        bool correct = true;
+        for (int y = 0; y < old->numRows; y++) {
+            for (int x = 0; x < old->numCols; x++) {
+                if (fabs(new->data.F32[y][x] / IMAGE_SIZE / IMAGE_SIZE - old->data.F32[y][x]) > TOL) {
+                    correct = false;
+                }
+            }
+        }
+        ok(correct, "new matches old");
+        psFree(new);
+        skip_end();
+        skip_end();
+
+        psFree(fftReal);
+        psFree(fftImag);
+        psFree(old);
+        ok(!psMemCheckLeaks(id, NULL, NULL, false), "no memory leaks");
+
+    }
+
+    {
+        // FFT convolution
+        psMemId id = psMemGetId();
+
+        psImage *image = generateImage();
+        psKernel *kernel = generateKernel();
+
+        psImage *convolved = psImageConvolveFFT(NULL, image, kernel, 0.0);
+        ok(convolved, "convolution result");
+        skip_start(!convolved, 3, "convolution failed");
+        ok(convolved->type.type == PS_TYPE_F32, "output type");
+        ok(convolved->numCols == IMAGE_SIZE && convolved->numRows == IMAGE_SIZE, "output size %dx%d",
+           convolved->numCols, convolved->numRows);
+        ok(checkConvolved(convolved, kernel), "convolved image correct");
+        psFree(convolved);
+        skip_end();
+
+        psFree(kernel);
+        psFree(image);
+
+        ok(!psMemCheckLeaks(id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    return exit_status();
+}
