/* This code is licensed to the public by its copyright owners under GPL. */ #define _XOPEN_SOURCE 500 /* get M_PI in math.h */ #include #include #include #include #include "mallocvar.h" #include "pm.h" #include "global_variables.h" #include "decode.h" #include "foveon.h" #include "stdio_nofail.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_nofail (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_nofail (ifp, 36, SEEK_SET); pm_readlittlelong(ifp, &fliplong); flip = fliplong; fseek_nofail (ifp, -4, SEEK_END); pm_readlittlelong(ifp, &pos); fseek_nofail (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_nofail(ifp); fseek_nofail (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_nofail (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_nofail (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_nofail (ifp, save, SEEK_SET); } is_foveon = 1; } void foveon_coeff(int * 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_nofail (ifp, meta_offset, SEEK_SET); pm_readlittlelong(ifp, &key); fread_nofail (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(Image const image) { 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_nofail(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(Image const image, 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[0])); for (row=0; row < height; ++row) { unsigned int i; for (i=0; i < 3; ++i) { unsigned int j; for (j = 0; j < 2; ++j) ddft[0][i][j] = ddft[1][i][j] + row / (height-1.0) * (ddft[2][i][j] - ddft[1][i][j]); } 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, 8 * sizeof(black[0])); memcpy (black+height-11, black+height-22, 11*(sizeof black[0])); 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++) { unsigned int i; for (i = 0; i < 3; ++i) { unsigned int j; for (j = 0; j < 2; ++j) ddft[0][i][j] = ddft[1][i][j] + row / (height-1.0) * (ddft[2][i][j] - ddft[1][i][j]); } 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; }