about summary refs log tree commit diff
path: root/converter/other/cameratopam/foveon.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/cameratopam/foveon.c')
-rw-r--r--converter/other/cameratopam/foveon.c790
1 files changed, 790 insertions, 0 deletions
diff --git a/converter/other/cameratopam/foveon.c b/converter/other/cameratopam/foveon.c
new file mode 100644
index 00000000..0198940c
--- /dev/null
+++ b/converter/other/cameratopam/foveon.c
@@ -0,0 +1,790 @@
+/* This code is licensed to the public by its copyright owners under GPL. */
+
+#define _XOPEN_SOURCE   /* get M_PI */
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <float.h>
+#include <math.h>
+
+#include "mallocvar.h"
+#include "pm.h"
+#include "global_variables.h"
+#include "decode.h"
+#include "foveon.h"
+
+#if HAVE_INT64
+   typedef int64_t INT64;
+   static bool const have64BitArithmetic = true;
+#else
+   /* We define INT64 to something that lets the code compile, but we
+      should not execute any INT64 code, because it will get the wrong
+      result.
+   */
+   typedef int INT64;
+   static bool const have64BitArithmetic = false;
+#endif
+
+
+#define FORC3 for (c=0; c < 3; c++)
+#define FORC4 for (c=0; c < colors; c++)
+
+
+
+static char *  
+foveon_gets(int    const offset, 
+            char * const str, 
+            int    const len) {
+
+    unsigned int i;
+    fseek (ifp, offset, SEEK_SET);
+    for (i=0; i < len-1; ++i) {
+        /* It certains seems wrong that we're reading a 16 bit integer
+           and assigning it to char, but that's what Dcraw does.
+        */
+        unsigned short val;
+        pm_readlittleshortu(ifp, &val);
+        str[i] = val;
+        if (str[i] == 0)
+            break;
+    }
+    str[i] = 0;
+    return str;
+}
+
+
+
+void 
+parse_foveon(FILE * const ifp) {
+    long fliplong;
+    long pos;
+    long magic;
+    long junk;
+    long entries;
+
+    fseek (ifp, 36, SEEK_SET);
+    pm_readlittlelong(ifp, &fliplong);
+    flip = fliplong;
+    fseek (ifp, -4, SEEK_END);
+    pm_readlittlelong(ifp, &pos);
+    fseek (ifp, pos, SEEK_SET);
+    pm_readlittlelong(ifp, &magic);
+    if (magic != 0x64434553) 
+        return; /* SECd */
+    pm_readlittlelong(ifp, &junk);
+    pm_readlittlelong(ifp, &entries);
+    while (entries--) {
+        long off, len, tag;
+        long save;
+        long pent;
+        int i, poff[256][2];
+        char name[64];
+        long sec_;
+
+        pm_readlittlelong(ifp, &off);
+        pm_readlittlelong(ifp, &len);
+        pm_readlittlelong(ifp, &tag);
+            
+        save = ftell(ifp);
+        fseek (ifp, off, SEEK_SET);
+        pm_readlittlelong(ifp, &sec_);
+        if (sec_ != (0x20434553 | (tag << 24))) return;
+        switch (tag) {
+        case 0x47414d49:          /* IMAG */
+            if (data_offset) 
+                break;
+            data_offset = off + 28;
+            fseek (ifp, 12, SEEK_CUR);
+            {
+                long wlong, hlong;
+                pm_readlittlelong(ifp, &wlong);
+                pm_readlittlelong(ifp, &hlong);
+                raw_width  = wlong;
+                raw_height = hlong;
+            }
+            break;
+        case 0x464d4143:          /* CAMF */
+            meta_offset = off + 24;
+            meta_length = len - 28;
+            if (meta_length > 0x20000)
+                meta_length = 0x20000;
+            break;
+        case 0x504f5250:          /* PROP */
+            pm_readlittlelong(ifp, &junk);
+            pm_readlittlelong(ifp, &pent);
+            fseek (ifp, 12, SEEK_CUR);
+            off += pent*8 + 24;
+            if (pent > 256) pent=256;
+            for (i=0; i < pent*2; i++) {
+                long x;
+                pm_readlittlelong(ifp, &x);
+                poff[0][i] = off + x*2;
+            }
+            for (i=0; i < pent; i++) {
+                foveon_gets (poff[i][0], name, 64);
+                if (!strcmp (name, "CAMMANUF"))
+                    foveon_gets (poff[i][1], make, 64);
+                if (!strcmp (name, "CAMMODEL"))
+                    foveon_gets (poff[i][1], model, 64);
+                if (!strcmp (name, "WB_DESC"))
+                    foveon_gets (poff[i][1], model2, 64);
+                if (!strcmp (name, "TIME"))
+                    timestamp = atoi (foveon_gets (poff[i][1], name, 64));
+            }
+        }
+        fseek (ifp, save, SEEK_SET);
+    }
+    is_foveon = 1;
+}
+
+
+
+void  
+foveon_coeff(bool * const useCoeffP,
+             float        coeff[3][4]) {
+
+    static const float foveon[3][3] =
+    { {  1.4032, -0.2231, -0.1016 },
+      { -0.5263,  1.4816,  0.0170 },
+      { -0.0112,  0.0183,  0.9113 } };
+
+    int i, j;
+
+    for (i=0; i < 3; ++i)
+        for (j=0; j < 3; ++j)
+            coeff[i][j] = foveon[i][j];
+    *useCoeffP = 1;
+}
+
+
+
+static void  
+foveon_decoder (unsigned int const huff[1024], 
+                unsigned int const code) {
+
+    struct decode *cur;
+    int len;
+    unsigned int code2;
+
+    cur = free_decode++;
+    if (free_decode > first_decode+2048) {
+        pm_error ("decoder table overflow");
+    }
+    if (code) {
+        unsigned int i;
+        for (i=0; i < 1024; i++) {
+            if (huff[i] == code) {
+                cur->leaf = i;
+                return;
+            }
+        }
+    }
+    if ((len = code >> 27) > 26) 
+        return;
+    code2 = (len+1) << 27 | (code & 0x3ffffff) << 1;
+
+    cur->branch[0] = free_decode;
+    foveon_decoder (huff, code2);
+    cur->branch[1] = free_decode;
+    foveon_decoder (huff, code2+1);
+}
+
+
+
+static void  
+foveon_load_camf() {
+    unsigned int i, val;
+    long key;
+
+    fseek (ifp, meta_offset, SEEK_SET);
+    pm_readlittlelong(ifp, &key);
+    fread (meta_data, 1, meta_length, ifp);
+    for (i=0; i < meta_length; i++) {
+        key = (key * 1597 + 51749) % 244944;
+        assert(have64BitArithmetic);
+        val = key * (INT64) 301593171 >> 24;
+        meta_data[i] ^= ((((key << 8) - val) >> 1) + val) >> 17;
+    }
+}
+
+
+
+void  
+foveon_load_raw() {
+
+    struct decode *dindex;
+    short diff[1024], pred[3];
+    unsigned int huff[1024], bitbuf=0;
+    int row, col, bit=-1, c, i;
+
+    assert(have64BitArithmetic);
+
+    for (i=0; i < 1024; ++i)
+        pm_readlittleshort(ifp, &diff[i]);
+
+    for (i=0; i < 1024; ++i) {
+        long x;
+        pm_readlittlelong(ifp, &x);
+        huff[i] = x;
+    }
+    init_decoder();
+    foveon_decoder (huff, 0);
+
+    for (row=0; row < height; row++) {
+        long junk;
+        memset (pred, 0, sizeof pred);
+        if (!bit) 
+            pm_readlittlelong(ifp, &junk);
+        for (col=bit=0; col < width; col++) {
+            FORC3 {
+                for (dindex=first_decode; dindex->branch[0]; ) {
+                    if ((bit = (bit-1) & 31) == 31)
+                        for (i=0; i < 4; i++)
+                            bitbuf = (bitbuf << 8) + fgetc(ifp);
+                    dindex = dindex->branch[bitbuf >> bit & 1];
+                }
+                pred[c] += diff[dindex->leaf];
+            }
+            FORC3 image[row*width+col][c] = pred[c];
+        }
+    }
+    foveon_load_camf();
+    maximum = clip_max = 0xffff;
+}
+
+
+
+static int
+sget4(char const s[]) {
+    return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
+}
+
+
+
+static char *  
+foveon_camf_param (const char * const block, 
+                   const char * const param) {
+    unsigned idx, num;
+    char *pos, *cp, *dp;
+
+    for (idx=0; idx < meta_length; idx += sget4(pos+8)) {
+        pos = meta_data + idx;
+        if (strncmp (pos, "CMb", 3)) break;
+        if (pos[3] != 'P') continue;
+        if (strcmp (block, pos+sget4(pos+12))) continue;
+        cp = pos + sget4(pos+16);
+        num = sget4(cp);
+        dp = pos + sget4(cp+4);
+        while (num--) {
+            cp += 8;
+            if (!strcmp (param, dp+sget4(cp)))
+                return dp+sget4(cp+4);
+        }
+    }
+    return NULL;
+}
+
+
+
+static void *  
+foveon_camf_matrix (int                dim[3], 
+                    const char * const name) {
+
+    unsigned i, idx, type, ndim, size, *mat;
+    char *pos, *cp, *dp;
+
+    for (idx=0; idx < meta_length; idx += sget4(pos+8)) {
+        pos = meta_data + idx;
+        if (strncmp (pos, "CMb", 3)) break;
+        if (pos[3] != 'M') continue;
+        if (strcmp (name, pos+sget4(pos+12))) continue;
+        dim[0] = dim[1] = dim[2] = 1;
+        cp = pos + sget4(pos+16);
+        type = sget4(cp);
+        if ((ndim = sget4(cp+4)) > 3) break;
+        dp = pos + sget4(cp+8);
+        for (i=ndim; i--; ) {
+            cp += 12;
+            dim[i] = sget4(cp);
+        }
+        if ((size = dim[0]*dim[1]*dim[2]) > meta_length/4) break;
+        MALLOCARRAY(mat, size);
+        if (mat == NULL)
+            pm_error("Unable to allocate memory for size=%d", size);
+        for (i=0; i < size; i++)
+            if (type && type != 6)
+                mat[i] = sget4(dp + i*4);
+            else
+                mat[i] = sget4(dp + i*2) & 0xffff;
+        return mat;
+    }
+    pm_message ("'%s' matrix not found!", name);
+    return NULL;
+}
+
+
+
+static int  
+foveon_fixed (void *       const ptr, 
+              int          const size, 
+              const char * const name) {
+    void *dp;
+    int dim[3];
+
+    dp = foveon_camf_matrix (dim, name);
+    if (!dp) return 0;
+    memcpy (ptr, dp, size*4);
+    free (dp);
+    return 1;
+}
+
+static float  foveon_avg (unsigned short *pix, int range[2], float cfilt)
+{
+    int i;
+    float val, min=FLT_MAX, max=-FLT_MAX, sum=0;
+
+    for (i=range[0]; i <= range[1]; i++) {
+        sum += val = 
+            (short)pix[i*4] + ((short)pix[i*4]-(short)pix[(i-1)*4]) * cfilt;
+        if (min > val) min = val;
+        if (max < val) max = val;
+    }
+    return (sum - min - max) / (range[1] - range[0] - 1);
+}
+
+static short *foveon_make_curve (double max, double mul, double filt)
+{
+    short *curve;
+    int size;
+    unsigned int i;
+    double x;
+
+    size = 4*M_PI*max / filt;
+    MALLOCARRAY(curve, size+1);
+    if (curve == NULL)
+        pm_error("Out of memory for %d-element curve array", size);
+
+    curve[0] = size;
+    for (i=0; i < size; ++i) {
+        x = i*filt/max/4;
+        curve[i+1] = (cos(x)+1)/2 * tanh(i*filt/mul) * mul + 0.5;
+    }
+    return curve;
+}
+
+static void foveon_make_curves
+(short **curvep, float dq[3], float div[3], float filt)
+{
+    double mul[3], max=0;
+    int c;
+
+    FORC3 mul[c] = dq[c]/div[c];
+    FORC3 if (max < mul[c]) max = mul[c];
+    FORC3 curvep[c] = foveon_make_curve (max, mul[c], filt);
+}
+
+static int  foveon_apply_curve (short *curve, int i)
+{
+    if (abs(i) >= (unsigned short)curve[0]) return 0;
+    return i < 0 ? -(unsigned short)curve[1-i] : (unsigned short)curve[1+i];
+}
+
+void  
+foveon_interpolate(float coeff[3][4]) {
+
+    static const short hood[] = { 
+        -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 };
+    short *pix, prev[3], *curve[8], (*shrink)[3];
+    float cfilt=0.8, ddft[3][3][2], ppm[3][3][3];
+    float cam_xyz[3][3], correct[3][3], last[3][3], trans[3][3];
+    float chroma_dq[3], color_dq[3], diag[3][3], div[3];
+    float (*black)[3], (*sgain)[3], (*sgrow)[3];
+    float fsum[3], val, frow, num;
+    int row, col, c, i, j, diff, sgx, irow, sum, min, max, limit;
+    int dim[3], dscr[2][2], (*smrow[7])[3], total[4], ipix[3];
+    int work[3][3], smlast, smred, smred_p=0, dev[3];
+    int satlev[3], keep[4], active[4];
+    unsigned *badpix;
+    double dsum=0, trsum[3];
+    char str[128], *cp;
+
+    foveon_fixed (dscr, 4, "DarkShieldColRange");
+    foveon_fixed (ppm[0][0], 27, "PostPolyMatrix");
+    foveon_fixed (ddft[1][0], 12, "DarkDrift");
+    foveon_fixed (&cfilt, 1, "ColumnFilter");
+    foveon_fixed (satlev, 3, "SaturationLevel");
+    foveon_fixed (keep, 4, "KeepImageArea");
+    foveon_fixed (active, 4, "ActiveImageArea");
+    foveon_fixed (chroma_dq, 3, "ChromaDQ");
+    foveon_fixed (color_dq, 3,
+                  foveon_camf_param ("IncludeBlocks", "ColorDQ") ?
+                  "ColorDQ" : "ColorDQCamRGB");
+
+    if (!(cp = foveon_camf_param ("WhiteBalanceIlluminants", model2)))
+    { pm_message ( "Invalid white balance \"%s\"", model2);
+    return; }
+    foveon_fixed (cam_xyz, 9, cp);
+    foveon_fixed (correct, 9,
+                  foveon_camf_param ("WhiteBalanceCorrections", model2));
+    memset (last, 0, sizeof last);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 last[i][j] += correct[i][c] * cam_xyz[c][j];
+
+    sprintf (str, "%sRGBNeutral", model2);
+    if (foveon_camf_param ("IncludeBlocks", str))
+        foveon_fixed (div, 3, str);
+    else {
+#define LAST(x,y) last[(i+x)%3][(c+y)%3]
+        for (i=0; i < 3; i++)
+            FORC3 diag[c][i] = LAST(1,1)*LAST(2,2) - LAST(1,2)*LAST(2,1);
+#undef LAST
+        FORC3 div[c] = 
+            diag[c][0]*0.3127 + diag[c][1]*0.329 + diag[c][2]*0.3583;
+    }
+    num = 0;
+    FORC3 if (num < div[c]) num = div[c];
+    FORC3 div[c] /= num;
+
+    memset (trans, 0, sizeof trans);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 trans[i][j] += coeff[i][c] * last[c][j] * div[j];
+    FORC3 trsum[c] = trans[c][0] + trans[c][1] + trans[c][2];
+    dsum = (6*trsum[0] + 11*trsum[1] + 3*trsum[2]) / 20;
+    for (i=0; i < 3; i++)
+        FORC3 last[i][c] = trans[i][c] * dsum / trsum[i];
+    memset (trans, 0, sizeof trans);
+    for (i=0; i < 3; i++)
+        for (j=0; j < 3; j++)
+            FORC3 trans[i][j] += (i==c ? 32 : -1) * last[c][j] / 30;
+
+    foveon_make_curves (curve, color_dq, div, cfilt);
+    FORC3 chroma_dq[c] /= 3;
+    foveon_make_curves (curve+3, chroma_dq, div, cfilt);
+    FORC3 dsum += chroma_dq[c] / div[c];
+    curve[6] = foveon_make_curve (dsum, dsum, cfilt);
+    curve[7] = foveon_make_curve (dsum*2, dsum*2, cfilt);
+
+    sgain = foveon_camf_matrix (dim, "SpatialGain");
+    if (!sgain) return;
+    sgrow = calloc (dim[1], sizeof *sgrow);
+    sgx = (width + dim[1]-2) / (dim[1]-1);
+
+    black = calloc (height, sizeof *black);
+    for (row=0; row < height; row++) {
+        for (i=0; i < 6; i++)
+            ddft[0][0][i] = ddft[1][0][i] +
+                row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]);
+        FORC3 black[row][c] =
+            ( foveon_avg (image[row*width]+c, dscr[0], cfilt) +
+              foveon_avg (image[row*width]+c, dscr[1], cfilt) * 3
+              - ddft[0][c][0] ) / 4 - ddft[0][c][1];
+    }
+    memcpy (black, black+8, sizeof *black*8);
+    memcpy (black+height-11, black+height-22, 11*sizeof *black);
+    memcpy (last, black, sizeof last);
+
+    for (row=1; row < height-1; row++) {
+        FORC3 if (last[1][c] > last[0][c]) {
+            if (last[1][c] > last[2][c])
+                black[row][c] = 
+                    (last[0][c] > last[2][c]) ? last[0][c]:last[2][c];
+        } else
+            if (last[1][c] < last[2][c])
+                black[row][c] = 
+                    (last[0][c] < last[2][c]) ? last[0][c]:last[2][c];
+        memmove (last, last+1, 2*sizeof last[0]);
+        memcpy (last[2], black[row+1], sizeof last[2]);
+    }
+    FORC3 black[row][c] = (last[0][c] + last[1][c])/2;
+    FORC3 black[0][c] = (black[1][c] + black[3][c])/2;
+
+    val = 1 - exp(-1/24.0);
+    memcpy (fsum, black, sizeof fsum);
+    for (row=1; row < height; row++)
+        FORC3 fsum[c] += black[row][c] =
+            (black[row][c] - black[row-1][c])*val + black[row-1][c];
+    memcpy (last[0], black[height-1], sizeof last[0]);
+    FORC3 fsum[c] /= height;
+    for (row = height; row--; )
+        FORC3 last[0][c] = black[row][c] =
+            (black[row][c] - fsum[c] - last[0][c])*val + last[0][c];
+
+    memset (total, 0, sizeof total);
+    for (row=2; row < height; row+=4)
+        for (col=2; col < width; col+=4) {
+            FORC3 total[c] += (short) image[row*width+col][c];
+            total[3]++;
+        }
+    for (row=0; row < height; row++)
+        FORC3 black[row][c] += fsum[c]/2 + total[c]/(total[3]*100.0);
+
+    for (row=0; row < height; row++) {
+        for (i=0; i < 6; i++)
+            ddft[0][0][i] = ddft[1][0][i] +
+                row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]);
+        pix = (short*)image[row*width];
+        memcpy (prev, pix, sizeof prev);
+        frow = row / (height-1.0) * (dim[2]-1);
+        if ((irow = frow) == dim[2]-1) irow--;
+        frow -= irow;
+        for (i=0; i < dim[1]; i++)
+            FORC3 sgrow[i][c] = sgain[ irow   *dim[1]+i][c] * (1-frow) +
+                sgain[(irow+1)*dim[1]+i][c] *    frow;
+        for (col=0; col < width; col++) {
+            FORC3 {
+                diff = pix[c] - prev[c];
+                prev[c] = pix[c];
+                ipix[c] = pix[c] + floor ((diff + (diff*diff >> 14)) * cfilt
+                                          - ddft[0][c][1] 
+                                          - ddft[0][c][0] 
+                                            * ((float) col/width - 0.5)
+                                          - black[row][c] );
+            }
+            FORC3 {
+                work[0][c] = ipix[c] * ipix[c] >> 14;
+                work[2][c] = ipix[c] * work[0][c] >> 14;
+                work[1][2-c] = ipix[(c+1) % 3] * ipix[(c+2) % 3] >> 14;
+            }
+            FORC3 {
+                for (val=i=0; i < 3; i++)
+                    for (  j=0; j < 3; j++)
+                        val += ppm[c][i][j] * work[i][j];
+                ipix[c] = floor ((ipix[c] + floor(val)) *
+                                 ( sgrow[col/sgx  ][c] * (sgx - col%sgx) +
+                                   sgrow[col/sgx+1][c] * (col%sgx) ) / sgx / 
+                                 div[c]);
+                if (ipix[c] > 32000) ipix[c] = 32000;
+                pix[c] = ipix[c];
+            }
+            pix += 4;
+        }
+    }
+    free (black);
+    free (sgrow);
+    free (sgain);
+
+    if ((badpix = foveon_camf_matrix (dim, "BadPixels"))) {
+        for (i=0; i < dim[0]; i++) {
+            col = (badpix[i] >> 8 & 0xfff) - keep[0];
+            row = (badpix[i] >> 20       ) - keep[1];
+            if ((unsigned)(row-1) > height-3 || (unsigned)(col-1) > width-3)
+                continue;
+            memset (fsum, 0, sizeof fsum);
+            for (sum=j=0; j < 8; j++)
+                if (badpix[i] & (1 << j)) {
+                    FORC3 fsum[c] += 
+                        image[(row+hood[j*2])*width+col+hood[j*2+1]][c];
+                    sum++;
+                }
+            if (sum) FORC3 image[row*width+col][c] = fsum[c]/sum;
+        }
+        free (badpix);
+    }
+
+    /* Array for 5x5 Gaussian averaging of red values */
+    smrow[6] = calloc (width*5, sizeof **smrow);
+    if (smrow[6] == NULL)
+        pm_error("Out of memory");
+    for (i=0; i < 5; i++)
+        smrow[i] = smrow[6] + i*width;
+
+    /* Sharpen the reds against these Gaussian averages */
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                smrow[4][col][0] =
+                    (pix[0]*6 + (pix[-4]+pix[4])*4 + pix[-8]+pix[8] + 8) >> 4;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            smred = ( 6 *  smrow[2][col][0]
+                      + 4 * (smrow[1][col][0] + smrow[3][col][0])
+                      +      smrow[0][col][0] + smrow[4][col][0] + 8 ) >> 4;
+            if (col == 2)
+                smred_p = smred;
+            i = pix[0] + ((pix[0] - ((smred*7 + smred_p) >> 3)) >> 3);
+            if (i > 32000) i = 32000;
+            pix[0] = i;
+            smred_p = smred;
+            pix += 4;
+        }
+    }
+
+    /* Adjust the brighter pixels for better linearity */
+    FORC3 {
+        i = satlev[c] / div[c];
+        if (maximum > i) maximum = i;
+    }
+    clip_max = maximum;
+    limit = maximum * 9 >> 4;
+    for (pix=(short*)image[0]; pix < (short *) image[height*width]; pix+=4) {
+        if (pix[0] <= limit || pix[1] <= limit || pix[2] <= limit)
+            continue;
+        min = max = pix[0];
+        for (c=1; c < 3; c++) {
+            if (min > pix[c]) min = pix[c];
+            if (max < pix[c]) max = pix[c];
+        }
+        i = 0x4000 - ((min - limit) << 14) / limit;
+        i = 0x4000 - (i*i >> 14);
+        i = i*i >> 14;
+        FORC3 pix[c] += (max - pix[c]) * i >> 14;
+    }
+    /*
+      Because photons that miss one detector often hit another,
+      the sum R+G+B is much less noisy than the individual colors.
+      So smooth the hues without smoothing the total.
+    */
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                FORC3 smrow[4][col][c] = (pix[c-4]+2*pix[c]+pix[c+4]+2) >> 2;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            FORC3 dev[c] = -foveon_apply_curve (curve[7], pix[c] -
+                                                ((smrow[1][col][c] + 
+                                                  2*smrow[2][col][c] + 
+                                                  smrow[3][col][c]) >> 2));
+            sum = (dev[0] + dev[1] + dev[2]) >> 3;
+            FORC3 pix[c] += dev[c] - sum;
+            pix += 4;
+        }
+    }
+    for (smlast=-1, row=2; row < height-2; row++) {
+        while (smlast < row+2) {
+            for (i=0; i < 6; i++)
+                smrow[(i+5) % 6] = smrow[i];
+            pix = (short*)image[++smlast*width+2];
+            for (col=2; col < width-2; col++) {
+                FORC3 smrow[4][col][c] =
+                    (pix[c-8]+pix[c-4]+pix[c]+pix[c+4]+pix[c+8]+2) >> 2;
+                pix += 4;
+            }
+        }
+        pix = (short*)image[row*width+2];
+        for (col=2; col < width-2; col++) {
+            for (total[3]=375, sum=60, c=0; c < 3; c++) {
+                for (total[c]=i=0; i < 5; i++)
+                    total[c] += smrow[i][col][c];
+                total[3] += total[c];
+                sum += pix[c];
+            }
+            if (sum < 0) sum = 0;
+            j = total[3] > 375 ? (sum << 16) / total[3] : sum * 174;
+            FORC3 pix[c] += foveon_apply_curve (curve[6],
+                                                ((j*total[c] + 0x8000) >> 16) 
+                                                - pix[c]);
+            pix += 4;
+        }
+    }
+
+    /* Transform the image to a different colorspace */
+    for (pix=(short*)image[0]; pix < (short *) image[height*width]; pix+=4) {
+        FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]);
+        sum = (pix[0]+pix[1]+pix[1]+pix[2]) >> 2;
+        FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]-sum);
+        FORC3 {
+            for (dsum=i=0; i < 3; i++)
+                dsum += trans[c][i] * pix[i];
+            if (dsum < 0)  dsum = 0;
+            if (dsum > 24000) dsum = 24000;
+            ipix[c] = dsum + 0.5;
+        }
+        FORC3 pix[c] = ipix[c];
+    }
+
+    /* Smooth the image bottom-to-top and save at 1/4 scale */
+    MALLOCARRAY(shrink, (width/4) * (height/4));
+    if (shrink == NULL)
+        pm_error("Out of memory allocating 1/4 scale array");
+
+    for (row = height/4; row > 0; --row) {
+        for (col=0; col < width/4; ++col) {
+            ipix[0] = ipix[1] = ipix[2] = 0;
+            for (i=0; i < 4; i++)
+                for (j=0; j < 4; j++)
+                    FORC3 ipix[c] += image[(row*4+i)*width+col*4+j][c];
+            FORC3
+                if (row+2 > height/4)
+                    shrink[row*(width/4)+col][c] = ipix[c] >> 4;
+                else
+                    shrink[row*(width/4)+col][c] =
+                        (shrink[(row+1)*(width/4)+col][c]*1840 + 
+                         ipix[c]*141 + 2048) >> 12;
+        }
+    }
+    /* From the 1/4-scale image, smooth right-to-left */
+    for (row=0; row < (height & ~3); ++row) {
+        ipix[0] = ipix[1] = ipix[2] = 0;
+        if ((row & 3) == 0)
+            for (col = width & ~3 ; col--; )
+                FORC3 smrow[0][col][c] = ipix[c] =
+                    (shrink[(row/4)*(width/4)+col/4][c]*1485 + 
+                     ipix[c]*6707 + 4096) >> 13;
+
+        /* Then smooth left-to-right */
+        ipix[0] = ipix[1] = ipix[2] = 0;
+        for (col=0; col < (width & ~3); col++)
+            FORC3 smrow[1][col][c] = ipix[c] =
+                (smrow[0][col][c]*1485 + ipix[c]*6707 + 4096) >> 13;
+
+        /* Smooth top-to-bottom */
+        if (row == 0)
+            memcpy (smrow[2], smrow[1], sizeof **smrow * width);
+        else
+            for (col=0; col < (width & ~3); col++)
+                FORC3 smrow[2][col][c] =
+                    (smrow[2][col][c]*6707 + smrow[1][col][c]*1485 + 4096) 
+                        >> 13;
+
+        /* Adjust the chroma toward the smooth values */
+        for (col=0; col < (width & ~3); col++) {
+            for (i=j=30, c=0; c < 3; c++) {
+                i += smrow[2][col][c];
+                j += image[row*width+col][c];
+            }
+            j = (j << 16) / i;
+            for (sum=c=0; c < 3; c++) {
+                ipix[c] = foveon_apply_curve(
+                    curve[c+3],
+                    ((smrow[2][col][c] * j + 0x8000) >> 16) - 
+                    image[row*width+col][c]);
+                sum += ipix[c];
+            }
+            sum >>= 3;
+            FORC3 {
+                i = image[row*width+col][c] + ipix[c] - sum;
+                if (i < 0) i = 0;
+                image[row*width+col][c] = i;
+            }
+        }
+    }
+    free (shrink);
+    free (smrow[6]);
+    for (i=0; i < 8; i++)
+        free (curve[i]);
+
+    /* Trim off the black border */
+    active[1] -= keep[1];
+    active[3] -= 2;
+    i = active[2] - active[0];
+    for (row = 0; row < active[3]-active[1]; row++)
+        memcpy (image[row*i], image[(row+active[1])*width+active[0]],
+                i * sizeof *image);
+    width = i;
+    height = row;
+}