diff --git a/pipi/filter/blur.c b/pipi/filter/blur.c
index 615788e..624b75d 100644
--- a/pipi/filter/blur.c
+++ b/pipi/filter/blur.c
@@ -115,27 +115,161 @@ pipi_image_t *pipi_gaussian_blur_ext(pipi_image_t *src, float rx, float ry,
     return ret;
 }
 
-/* FIXME: box blur would be incredibly faster using an accumulator instead
- * of a convolution filter... */
 pipi_image_t *pipi_box_blur(pipi_image_t *src, int size)
 {
     return pipi_box_blur_ext(src, size, size);
 }
 
+/* FIXME: split this into templates for wrap-around and proper gray support */
 pipi_image_t *pipi_box_blur_ext(pipi_image_t *src, int m, int n)
 {
-    pipi_image_t *ret;
-    double *kernel;
-    int i;
+    pipi_image_t *dst;
+    pipi_pixels_t *srcp, *dstp;
+    float *srcdata, *dstdata;
+    double *acc;
+    int x, y, w, h, i, j, size, gray;
 
-    kernel = malloc(m * n * sizeof(double));
-    for(i = 0; i < m * n; i++)
-        kernel[i] = 1. / (m * n);
+    w = src->w;
+    h = src->h;
+    size = (2 * m + 1) * (2 * n + 1);
 
-    ret = pipi_convolution(src, m, n, kernel);
+    gray = (src->last_modified == PIPI_PIXELS_Y_F);
 
-    free(kernel);
+    srcp = gray ? pipi_getpixels(src, PIPI_PIXELS_Y_F)
+                : pipi_getpixels(src, PIPI_PIXELS_RGBA_F);
+    srcdata = (float *)srcp->pixels;
 
-    return ret;
+    dst = pipi_new(w, h);
+    dstp = gray ? pipi_getpixels(dst, PIPI_PIXELS_Y_F)
+                : pipi_getpixels(dst, PIPI_PIXELS_RGBA_F);
+    dstdata = (float *)dstp->pixels;
+
+    acc = malloc(w * (gray ? 1 : 4) * sizeof(double));
+
+    /* Step 1: fill the accumulator */
+    for(x = 0; x < w; x++)
+    {
+        if(gray)
+        {
+            double t = 0.;
+
+            for(j = -n; j <= n; j++)
+            {
+                int j2 = (j < 0) ? h - 1 - ((-j - 1) % h) : j % h;
+                t += srcdata[j2 * w + x];
+            }
+
+            acc[x] = t;
+        }
+        else
+        {
+            double r = 0., g = 0., b = 0., a = 0.;
+
+            for(j = -n; j <= n; j++)
+            {
+                int j2 = (j < 0) ? h - 1 - ((-j - 1) % h) : j % h;
+                r += srcdata[4 * (j2 * w + x)];
+                g += srcdata[4 * (j2 * w + x) + 1];
+                b += srcdata[4 * (j2 * w + x) + 2];
+                a += srcdata[4 * (j2 * w + x) + 3];
+            }
+
+            acc[4 * x] = r;
+            acc[4 * x + 1] = g;
+            acc[4 * x + 2] = b;
+            acc[4 * x + 3] = a;
+        }
+    }
+
+    /* Step 2: blur the image, line by line */
+    for(y = 0; y < h; y++)
+    {
+        double r = 0., g = 0., b = 0., a = 0.;
+        double t = 0.;
+
+        /* 2.1: compute the first pixel */
+        if(gray)
+        {
+            for(i = -m; i <= m; i++)
+            {
+                int i2 = (i < 0) ? w - 1 - ((-i - 1) % w) : i % w;
+                t += acc[i2];
+            }
+        }
+        else
+        {
+            for(i = -m; i <= m; i++)
+            {
+                int i2 = (i < 0) ? w - 1 - ((-i - 1) % w) : i % w;
+                r += acc[4 * i2];
+                g += acc[4 * i2 + 1];
+                b += acc[4 * i2 + 2];
+                a += acc[4 * i2 + 3];
+            }
+        }
+
+        /* 2.2: iterate on the whole line */
+        for(x = 0; x < w; x++)
+        {
+            int u, u2, v, v2;
+
+            if(gray)
+            {
+                dstdata[y * w + x] = t / size;
+            }
+            else
+            {
+                dstdata[4 * (y * w + x)] = r / size;
+                dstdata[4 * (y * w + x) + 1] = g / size;
+                dstdata[4 * (y * w + x) + 2] = b / size;
+                dstdata[4 * (y * w + x) + 3] = a / size;
+            }
+
+            u = x - m;
+            u2 = (u < 0) ? w - 1 - ((-u - 1) % w) : u % w;
+            v = x + m + 1;
+            v2 = (v < 0) ? w - 1 - ((-v - 1) % w) : v % w;
+            if(gray)
+            {
+                t = t - acc[u2] + acc[v2];
+            }
+            else
+            {
+                r = r - acc[4 * u2] + acc[4 * v2];
+                g = g - acc[4 * u2 + 1] + acc[4 * v2 + 1];
+                b = b - acc[4 * u2 + 2] + acc[4 * v2 + 2];
+                a = a - acc[4 * u2 + 3] + acc[4 * v2 + 3];
+            }
+        }
+
+        /* 2.3: update the accumulator */
+        for(x = 0; x < w; x++)
+        {
+            int u, u2, v, v2;
+
+            u = y - n;
+            u2 = (u < 0) ? w - 1 - ((-u - 1) % w) : u % w;
+            v = y + n + 1;
+            v2 = (v < 0) ? w - 1 - ((-v - 1) % w) : v % w;
+            if(gray)
+            {
+                acc[x] += srcdata[v2 * w + x] - srcdata[u2 * w + x];
+            }
+            else
+            {
+                int uoff = 4 * (u2 * w + x);
+                int voff = 4 * (v2 * w + x);
+
+                acc[4 * x] += srcdata[voff] - srcdata[uoff];
+                acc[4 * x + 1] += srcdata[voff + 1] - srcdata[uoff + 1];
+                acc[4 * x + 2] += srcdata[voff + 2] - srcdata[uoff + 2];
+                acc[4 * x + 3] += srcdata[voff + 3] - srcdata[uoff + 3];
+            }
+        }
+    }
+
+    free(acc);
+
+    return dst;
 }