about summary refs log tree commit diff
path: root/converter/other/cameratopam/cameratopam.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/cameratopam/cameratopam.c')
-rw-r--r--converter/other/cameratopam/cameratopam.c906
1 files changed, 906 insertions, 0 deletions
diff --git a/converter/other/cameratopam/cameratopam.c b/converter/other/cameratopam/cameratopam.c
new file mode 100644
index 00000000..92773c91
--- /dev/null
+++ b/converter/other/cameratopam/cameratopam.c
@@ -0,0 +1,906 @@
+/*
+   This is derived from Dave Coffin's raw photo decoder, dcraw.c,
+   Copyright 1997-2005 by Dave Coffin, dcoffin a cybercom o net.
+
+   See the COPYRIGHT file in the same directory as this file for
+   information on copyright and licensing.
+ */
+
+
+#define _BSD_SOURCE 1   /* Make sure string.h contains strcasecmp() */
+#define _XOPEN_SOURCE  /* Make sure unistd.h contains swab() */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __CYGWIN__
+#include <io.h>
+#endif
+#ifdef WIN32
+  #include <winsock2.h>
+  #pragma comment(lib, "ws2_32.lib")
+  #define strcasecmp stricmp
+  typedef __int64 INT64;
+  static bool const have64BitArithmetic = true;
+#else
+  #include <unistd.h>
+#endif
+
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+#include "global_variables.h"
+#include "util.h"
+#include "decode.h"
+#include "identify.h"
+#include "bayer.h"
+#include "foveon.h"
+#include "dng.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
+
+/*
+   All global variables are defined here, and all functions that
+   access them are prefixed with "CLASS".  Note that a thread-safe
+   C++ class cannot have non-const static local variables.
+ */
+FILE * ifp;
+short order;
+char make[64], model[70], model2[64], *meta_data;
+time_t timestamp;
+int data_offset, meta_offset, meta_length;
+int tiff_data_compression, kodak_data_compression;
+int raw_height, raw_width, top_margin, left_margin;
+int height, width, fuji_width, colors, tiff_samples;
+int black, maximum, clip_max;
+int iheight, iwidth, shrink;
+int is_dng, is_canon, is_foveon, use_coeff, use_gamma;
+int trim, flip, xmag, ymag;
+int zero_after_ff;
+unsigned filters;
+unsigned short (*image)[4], white[8][8], curve[0x1000];
+int fuji_secondary;
+float cam_mul[4], pre_mul[4], coeff[3][4];
+int histogram[3][0x2000];
+jmp_buf failure;
+bool use_secondary;
+bool verbose;
+
+#ifdef USE_LCMS
+#include <lcms.h>
+int profile_offset, profile_length;
+#endif
+
+#define CLASS
+
+#define FORC3 for (c=0; c < 3; c++)
+#define FORC4 for (c=0; c < colors; c++)
+
+static void CLASS merror (const void *ptr, const char *where)
+{
+    if (ptr == NULL)
+        pm_error ("Out of memory in %s", where);
+}
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* "-" means Standard Input */
+    float bright;
+    float red_scale;
+    float blue_scale;
+    const char * profile;
+    unsigned int identify_only;
+    unsigned int verbose;
+    unsigned int half_size;
+    unsigned int four_color_rgb;
+    unsigned int document_mode;
+    unsigned int quick_interpolate;
+    unsigned int use_auto_wb;
+    unsigned int use_camera_wb;
+    unsigned int use_camera_rgb;
+    unsigned int use_secondary;
+    unsigned int no_clip_color;
+    unsigned int linear;
+};
+
+
+static struct cmdlineInfo cmdline;
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo *cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdlineP structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;
+    optEntry *option_def;
+    unsigned int option_def_index;
+    unsigned int brightSpec, red_scaleSpec, blue_scaleSpec,
+        profileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "bright", 
+            OPT_FLOAT,   &cmdlineP->bright,     &brightSpec, 0);
+    OPTENT3(0, "red_scale", 
+            OPT_FLOAT,   &cmdlineP->red_scale,  &red_scaleSpec, 0);
+    OPTENT3(0, "blue_scale", 
+            OPT_FLOAT,   &cmdlineP->blue_scale, &blue_scaleSpec, 0);
+    OPTENT3(0, "profile", 
+            OPT_STRING,  &cmdlineP->profile,    &profileSpec, 0);
+    OPTENT3(0,   "identify_only",   
+            OPT_FLAG,    NULL, &cmdlineP->identify_only, 0);
+    OPTENT3(0,   "verbose",   
+            OPT_FLAG,    NULL, &cmdlineP->verbose, 0);
+    OPTENT3(0,   "half_size",   
+            OPT_FLAG,    NULL, &cmdlineP->half_size, 0);
+    OPTENT3(0,   "four_color_rgb",   
+            OPT_FLAG,    NULL, &cmdlineP->four_color_rgb, 0);
+    OPTENT3(0,   "document_mode",   
+            OPT_FLAG,    NULL, &cmdlineP->document_mode, 0);
+    OPTENT3(0,   "quick_interpolate",   
+            OPT_FLAG,    NULL, &cmdlineP->quick_interpolate, 0);
+    OPTENT3(0,   "balance_auto",   
+            OPT_FLAG,    NULL, &cmdlineP->use_auto_wb, 0);
+    OPTENT3(0,   "balance_camera",   
+            OPT_FLAG,    NULL, &cmdlineP->use_camera_wb, 0);
+    OPTENT3(0,   "use_secondary",   
+            OPT_FLAG,    NULL, &cmdlineP->use_secondary, 0);
+    OPTENT3(0,   "no_clip_color",   
+            OPT_FLAG,    NULL, &cmdlineP->no_clip_color, 0);
+    OPTENT3(0,   "rgb",   
+            OPT_FLAG,    NULL, &cmdlineP->use_camera_rgb, 0);
+    OPTENT3(0,   "linear",   
+            OPT_FLAG,    NULL, &cmdlineP->linear, 0);
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+
+    if (!brightSpec)
+        cmdlineP->bright = 1.0;
+    if (!red_scaleSpec)
+        cmdlineP->red_scale = 1.0;
+    if (!blue_scaleSpec)
+        cmdlineP->blue_scale = 1.0;
+    if (!profileSpec)
+        cmdlineP->profile = NULL;
+
+
+    if (argc - 1 == 0)
+        cmdlineP->inputFileName = strdup("-");  /* he wants stdin */
+    else if (argc - 1 == 1)
+        cmdlineP->inputFileName = strdup(argv[1]);
+    else 
+        pm_error("Too many arguments.  The only argument accepted "
+                 "is the input file name");
+}
+
+  
+/*
+   Seach from the current directory up to the root looking for
+   a ".badpixels" file, and fix those pixels now.
+ */
+static void CLASS bad_pixels()
+{
+  FILE *fp=NULL;
+  char *fname, *cp, line[128];
+  int len, time, row, col, r, c, rad, tot, n, fixed=0;
+
+  if (!filters) return;
+  for (len=16 ; ; len *= 2) {
+    fname = malloc (len);
+    if (!fname) return;
+    if (getcwd (fname, len-12)) break;
+    free (fname);
+    if (errno != ERANGE) return;
+  }
+#ifdef WIN32
+  if (fname[1] == ':')
+    memmove (fname, fname+2, len-2);
+  for (cp=fname; *cp; cp++)
+    if (*cp == '\\') *cp = '/';
+#endif
+  cp = fname + strlen(fname);
+  if (cp[-1] == '/') cp--;
+  while (*fname == '/') {
+    strcpy (cp, "/.badpixels");
+    if ((fp = fopen (fname, "r"))) break;
+    if (cp == fname) break;
+    while (*--cp != '/');
+  }
+  free (fname);
+  if (!fp) return;
+  while (fgets (line, 128, fp)) {
+    cp = strchr (line, '#');
+    if (cp) *cp = 0;
+    if (sscanf (line, "%d %d %d", &col, &row, &time) != 3) continue;
+    if ((unsigned) col >= width || (unsigned) row >= height) continue;
+    if (time > timestamp) continue;
+    for (tot=n=0, rad=1; rad < 3 && n==0; rad++)
+      for (r = row-rad; r <= row+rad; r++)
+    for (c = col-rad; c <= col+rad; c++)
+      if ((unsigned) r < height && (unsigned) c < width &&
+        (r != row || c != col) && FC(r,c) == FC(row,col)) {
+        tot += BAYER(r,c);
+        n++;
+      }
+    BAYER(row,col) = tot/n;
+    if (cmdline.verbose) {
+      if (!fixed++)
+          pm_message ("Fixed bad pixels at: %d,%d", col, row);
+    }
+  }
+  fclose (fp);
+}
+
+static void CLASS scale_colors()
+{
+  int row, col, c, val, shift=0;
+  int min[4], max[4], count[4];
+  double sum[4], dmin;
+
+  maximum -= black;
+  if (cmdline.use_auto_wb || (cmdline.use_camera_wb && camera_red == -1)) {
+    FORC4 min[c] = INT_MAX;
+    FORC4 max[c] = count[c] = sum[c] = 0;
+    for (row=0; row < height; row++)
+      for (col=0; col < width; col++)
+    FORC4 {
+      val = image[row*width+col][c];
+      if (!val) continue;
+      if (min[c] > val) min[c] = val;
+      if (max[c] < val) max[c] = val;
+      val -= black;
+      if (val > maximum-25) continue;
+      if (val < 0) val = 0;
+      sum[c] += val;
+      count[c]++;
+    }
+    FORC4 pre_mul[c] = count[c] / sum[c];
+  }
+  if (cmdline.use_camera_wb && camera_red != -1) {
+    FORC4 count[c] = sum[c] = 0;
+    for (row=0; row < 8; row++)
+      for (col=0; col < 8; col++) {
+    c = FC(row,col);
+    if ((val = white[row][col] - black) > 0)
+      sum[c] += val;
+    count[c]++;
+      }
+    val = 1;
+    FORC4 if (sum[c] == 0) val = 0;
+    if (val)
+      FORC4 pre_mul[c] = count[c] / sum[c];
+    else if (camera_red && camera_blue)
+      memcpy (pre_mul, cam_mul, sizeof pre_mul);
+    else
+      pm_message ("Cannot use camera white balance.");
+  }
+  if (!use_coeff) {
+    pre_mul[0] *= cmdline.red_scale;
+    pre_mul[2] *= cmdline.blue_scale;
+  }
+  dmin = DBL_MAX;
+  FORC4 if (dmin > pre_mul[c])
+        dmin = pre_mul[c];
+  FORC4 pre_mul[c] /= dmin;
+
+  while (maximum << shift < 0x8000) shift++;
+  FORC4 pre_mul[c] *= 1 << shift;
+  maximum <<= shift;
+
+  if (cmdline.linear || cmdline.bright < 1) {
+      maximum *= cmdline.bright;
+      if (maximum > 0xffff)
+          maximum = 0xffff;
+      FORC4 pre_mul[c] *= cmdline.bright;
+  }
+  if (cmdline.verbose) {
+    fprintf (stderr, "Scaling with black=%d, pre_mul[] =", black);
+    FORC4 fprintf (stderr, " %f", pre_mul[c]);
+    fputc ('\n', stderr);
+  }
+  clip_max = cmdline.no_clip_color ? 0xffff : maximum;
+  for (row=0; row < height; row++)
+    for (col=0; col < width; col++)
+      FORC4 {
+    val = image[row*width+col][c];
+    if (!val) continue;
+    val -= black;
+    val *= pre_mul[c];
+    if (val < 0) val = 0;
+    if (val > clip_max) val = clip_max;
+    image[row*width+col][c] = val;
+      }
+}
+
+/*
+   This algorithm is officially called:
+
+   "Interpolation using a Threshold-based variable number of gradients"
+
+   described in http://www-ise.stanford.edu/~tingchen/algodep/vargra.html
+
+   I've extended the basic idea to work with non-Bayer filter arrays.
+   Gradients are numbered clockwise from NW=0 to W=7.
+ */
+static void CLASS vng_interpolate()
+{
+  static const signed char *cp, terms[] = {
+    -2,-2,+0,-1,0,(char)0x01, -2,-2,+0,+0,1,(char)0x01, -2,-1,-1,+0,0,(char)0x01,
+    -2,-1,+0,-1,0,(char)0x02, -2,-1,+0,+0,0,(char)0x03, -2,-1,+0,+1,1,(char)0x01,
+    -2,+0,+0,-1,0,(char)0x06, -2,+0,+0,+0,1,(char)0x02, -2,+0,+0,+1,0,(char)0x03,
+    -2,+1,-1,+0,0,(char)0x04, -2,+1,+0,-1,1,(char)0x04, -2,+1,+0,+0,0,(char)0x06,
+    -2,+1,+0,+1,0,(char)0x02, -2,+2,+0,+0,1,(char)0x04, -2,+2,+0,+1,0,(char)0x04,
+    -1,-2,-1,+0,0,(char)0x80, -1,-2,+0,-1,0,(char)0x01, -1,-2,+1,-1,0,(char)0x01,
+    -1,-2,+1,+0,1,(char)0x01, -1,-1,-1,+1,0,(char)0x88, -1,-1,+1,-2,0,(char)0x40,
+    -1,-1,+1,-1,0,(char)0x22, -1,-1,+1,+0,0,(char)0x33, -1,-1,+1,+1,1,(char)0x11,
+    -1,+0,-1,+2,0,(char)0x08, -1,+0,+0,-1,0,(char)0x44, -1,+0,+0,+1,0,(char)0x11,
+    -1,+0,+1,-2,1,(char)0x40, -1,+0,+1,-1,0,(char)0x66, -1,+0,+1,+0,1,(char)0x22,
+    -1,+0,+1,+1,0,(char)0x33, -1,+0,+1,+2,1,(char)0x10, -1,+1,+1,-1,1,(char)0x44,
+    -1,+1,+1,+0,0,(char)0x66, -1,+1,+1,+1,0,(char)0x22, -1,+1,+1,+2,0,(char)0x10,
+    -1,+2,+0,+1,0,(char)0x04, -1,+2,+1,+0,1,(char)0x04, -1,+2,+1,+1,0,(char)0x04,
+    +0,-2,+0,+0,1,(char)0x80, +0,-1,+0,+1,1,(char)0x88, +0,-1,+1,-2,0,(char)0x40,
+    +0,-1,+1,+0,0,(char)0x11, +0,-1,+2,-2,0,(char)0x40, +0,-1,+2,-1,0,(char)0x20,
+    +0,-1,+2,+0,0,(char)0x30, +0,-1,+2,+1,1,(char)0x10, +0,+0,+0,+2,1,(char)0x08,
+    +0,+0,+2,-2,1,(char)0x40, +0,+0,+2,-1,0,(char)0x60, +0,+0,+2,+0,1,(char)0x20,
+    +0,+0,+2,+1,0,(char)0x30, +0,+0,+2,+2,1,(char)0x10, +0,+1,+1,+0,0,(char)0x44,
+    +0,+1,+1,+2,0,(char)0x10, +0,+1,+2,-1,1,(char)0x40, +0,+1,+2,+0,0,(char)0x60,
+    +0,+1,+2,+1,0,(char)0x20, +0,+1,+2,+2,0,(char)0x10, +1,-2,+1,+0,0,(char)0x80,
+    +1,-1,+1,+1,0,(char)0x88, +1,+0,+1,+2,0,(char)0x08, +1,+0,+2,-1,0,(char)0x40,
+    +1,+0,+2,+1,0,(char)0x10
+  }, chood[] = { -1,-1, -1,0, -1,+1, 0,+1, +1,+1, +1,0, +1,-1, 0,-1 };
+  unsigned short (*brow[5])[4], *pix;
+  int code[8][2][320], *ip, gval[8], gmin, gmax, sum[4];
+  int row, col, shift, x, y, x1, x2, y1, y2, t, weight, grads, color, diag;
+  int g, diff, thold, num, c;
+
+  for (row=0; row < 8; row++) {     /* Precalculate for bilinear */
+    for (col=1; col < 3; col++) {
+      ip = code[row][col & 1];
+      memset (sum, 0, sizeof sum);
+      for (y=-1; y <= 1; y++)
+    for (x=-1; x <= 1; x++) {
+      shift = (y==0) + (x==0);
+      if (shift == 2) continue;
+      color = FC(row+y,col+x);
+      *ip++ = (width*y + x)*4 + color;
+      *ip++ = shift;
+      *ip++ = color;
+      sum[color] += 1 << shift;
+    }
+      FORC4
+    if (c != FC(row,col)) {
+      *ip++ = c;
+      *ip++ = sum[c];
+    }
+    }
+  }
+  for (row=1; row < height-1; row++) {  /* Do bilinear interpolation */
+    for (col=1; col < width-1; col++) {
+      pix = image[row*width+col];
+      ip = code[row & 7][col & 1];
+      memset (sum, 0, sizeof sum);
+      for (g=8; g--; ) {
+    diff = pix[*ip++];
+    diff <<= *ip++;
+    sum[*ip++] += diff;
+      }
+      for (g=colors; --g; ) {
+    c = *ip++;
+    pix[c] = sum[c] / *ip++;
+      }
+    }
+  }
+  if (cmdline.quick_interpolate)
+    return;
+
+  for (row=0; row < 8; row++) {     /* Precalculate for VNG */
+    for (col=0; col < 2; col++) {
+      ip = code[row][col];
+      for (cp=terms, t=0; t < 64; t++) {
+    y1 = *cp++;  x1 = *cp++;
+    y2 = *cp++;  x2 = *cp++;
+    weight = *cp++;
+    grads = *cp++;
+    color = FC(row+y1,col+x1);
+    if (FC(row+y2,col+x2) != color) continue;
+    diag = (FC(row,col+1) == color && FC(row+1,col) == color) ? 2:1;
+    if (abs(y1-y2) == diag && abs(x1-x2) == diag) continue;
+    *ip++ = (y1*width + x1)*4 + color;
+    *ip++ = (y2*width + x2)*4 + color;
+    *ip++ = weight;
+    for (g=0; g < 8; g++)
+      if (grads & 1<<g) *ip++ = g;
+    *ip++ = -1;
+      }
+      *ip++ = INT_MAX;
+      for (cp=chood, g=0; g < 8; g++) {
+    y = *cp++;  x = *cp++;
+    *ip++ = (y*width + x) * 4;
+    color = FC(row,col);
+    if (FC(row+y,col+x) != color && FC(row+y*2,col+x*2) == color)
+      *ip++ = (y*width + x) * 8 + color;
+    else
+      *ip++ = 0;
+      }
+    }
+  }
+  brow[4] = calloc (width*3, sizeof **brow);
+  merror (brow[4], "vng_interpolate()");
+  for (row=0; row < 3; row++)
+    brow[row] = brow[4] + row*width;
+  for (row=2; row < height-2; row++) {      /* Do VNG interpolation */
+    for (col=2; col < width-2; col++) {
+      pix = image[row*width+col];
+      ip = code[row & 7][col & 1];
+      memset (gval, 0, sizeof gval);
+      while ((g = ip[0]) != INT_MAX) {      /* Calculate gradients */
+    num = (diff = pix[g] - pix[ip[1]]) >> 31;
+    gval[ip[3]] += (diff = ((diff ^ num) - num) << ip[2]);
+    ip += 5;
+    if ((g = ip[-1]) == -1) continue;
+    gval[g] += diff;
+    while ((g = *ip++) != -1)
+      gval[g] += diff;
+      }
+      ip++;
+      gmin = gmax = gval[0];            /* Choose a threshold */
+      for (g=1; g < 8; g++) {
+    if (gmin > gval[g]) gmin = gval[g];
+    if (gmax < gval[g]) gmax = gval[g];
+      }
+      if (gmax == 0) {
+    memcpy (brow[2][col], pix, sizeof *image);
+    continue;
+      }
+      thold = gmin + (gmax >> 1);
+      memset (sum, 0, sizeof sum);
+      color = FC(row,col);
+      for (num=g=0; g < 8; g++,ip+=2) {     /* Average the neighbors */
+    if (gval[g] <= thold) {
+      FORC4
+        if (c == color && ip[1])
+          sum[c] += (pix[c] + pix[ip[1]]) >> 1;
+        else
+          sum[c] += pix[ip[0] + c];
+      num++;
+    }
+      }
+      FORC4 {                   /* Save to buffer */
+    t = pix[color];
+    if (c != color) {
+      t += (sum[c] - sum[color])/num;
+      if (t < 0) t = 0;
+      if (t > clip_max) t = clip_max;
+    }
+    brow[2][col][c] = t;
+      }
+    }
+    if (row > 3)                /* Write buffer to image */
+      memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image);
+    for (g=0; g < 4; g++)
+      brow[(g-1) & 3] = brow[g];
+  }
+  memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image);
+  memcpy (image[(row-1)*width+2], brow[1]+2, (width-4)*sizeof *image);
+  free (brow[4]);
+}
+
+#ifdef USE_LCMS
+static void 
+apply_profile(FILE *       const ifP,
+              const char * const pfname)
+{
+  char *prof;
+  cmsHPROFILE hInProfile=NULL, hOutProfile;
+  cmsHTRANSFORM hTransform;
+
+  if (pfname)
+    hInProfile = cmsOpenProfileFromFile (pfname, "r");
+  else if (profile_length) {
+    prof = malloc (profile_length);
+    merror (prof, "apply_profile()");
+    fseek (ifP, profile_offset, SEEK_SET);
+    fread (prof, 1, profile_length, ifP);
+    hInProfile = cmsOpenProfileFromMem (prof, profile_length);
+    free (prof);
+  }
+  if (!hInProfile) return;
+  if (cmdline.verbose)
+      pm_message( "Applying color profile...");
+  maximum = 0xffff;
+  use_gamma = use_coeff = 0;
+
+  hOutProfile = cmsCreate_sRGBProfile();
+  hTransform = cmsCreateTransform (hInProfile, TYPE_RGBA_16,
+    hOutProfile, TYPE_RGBA_16, INTENT_PERCEPTUAL, 0);
+  cmsDoTransform (hTransform, image, image, width*height);
+
+  cmsDeleteTransform (hTransform);
+  cmsCloseProfile (hInProfile);
+  cmsCloseProfile (hOutProfile);
+}
+#else
+static void 
+apply_profile(FILE *       const ifP,
+              const char * const pfname)
+{
+}
+#endif
+
+/*
+   Convert the entire image to RGB colorspace and build a histogram.
+ */
+static void CLASS convert_to_rgb()
+{
+  int row, col, r, g, c=0;
+  unsigned short *img;
+  float rgb[3];
+
+  if (cmdline.document_mode)
+    colors = 1;
+  memset (histogram, 0, sizeof histogram);
+  for (row = trim; row < height-trim; row++)
+    for (col = trim; col < width-trim; col++) {
+      img = image[row*width+col];
+      if (cmdline.document_mode)
+    c = FC(row,col);
+      if (colors == 4 && !use_coeff)    /* Recombine the greens */
+    img[1] = (img[1] + img[3]) >> 1;
+      if (colors == 1)          /* RGB from grayscale */
+    for (r=0; r < 3; r++)
+      rgb[r] = img[c];
+      else if (use_coeff) {     /* RGB via coeff[][] */
+    for (r=0; r < 3; r++)
+      for (rgb[r]=g=0; g < colors; g++)
+        rgb[r] += img[g] * coeff[r][g];
+      } else                /* RGB from RGB (easy) */
+    goto norgb;
+      for (r=0; r < 3; r++) {
+    if (rgb[r] < 0)        rgb[r] = 0;
+    if (rgb[r] > clip_max) rgb[r] = clip_max;
+    img[r] = rgb[r];
+      }
+norgb:
+      for (r=0; r < 3; r++)
+    histogram[r][img[r] >> 3]++;
+    }
+}
+
+static void CLASS fuji_rotate()
+{
+  int i, wide, high, row, col;
+  double step;
+  float r, c, fr, fc;
+  unsigned ur, uc;
+  unsigned short (*img)[4], (*pix)[4];
+
+  if (!fuji_width) return;
+  if (cmdline.verbose)
+    pm_message ("Rotating image 45 degrees...");
+  fuji_width = (fuji_width + shrink) >> shrink;
+  step = sqrt(0.5);
+  wide = fuji_width / step;
+  high = (height - fuji_width) / step;
+  img = calloc (wide*high, sizeof *img);
+  merror (img, "fuji_rotate()");
+
+  for (row=0; row < high; row++)
+    for (col=0; col < wide; col++) {
+      ur = r = fuji_width + (row-col)*step;
+      uc = c = (row+col)*step;
+      if (ur > height-2 || uc > width-2) continue;
+      fr = r - ur;
+      fc = c - uc;
+      pix = image + ur*width + uc;
+      for (i=0; i < colors; i++)
+    img[row*wide+col][i] =
+      (pix[    0][i]*(1-fc) + pix[      1][i]*fc) * (1-fr) +
+      (pix[width][i]*(1-fc) + pix[width+1][i]*fc) * fr;
+    }
+  free (image);
+  width  = wide;
+  height = high;
+  image  = img;
+  fuji_width = 0;
+}
+
+static void CLASS flip_image()
+{
+    unsigned *flag;
+    int size, base, dest, next, row, col, temp;
+
+    struct imageCell {
+        unsigned char contents[8];
+    };
+    struct imageCell * img;
+    struct imageCell hold;
+
+    switch ((flip+3600) % 360) {
+    case 270:  flip = 5;  break;
+    case 180:  flip = 3;  break;
+    case  90:  flip = 6;
+    }
+    img = (struct imageCell *) image;
+    size = height * width;
+    flag = calloc ((size+31) >> 5, sizeof *flag);
+    merror (flag, "flip_image()");
+    for (base = 0; base < size; base++) {
+        if (flag[base >> 5] & (1 << (base & 31)))
+            continue;
+        dest = base;
+        hold = img[base];
+        while (1) {
+            if (flip & 4) {
+                row = dest % height;
+                col = dest / height;
+            } else {
+                row = dest / width;
+                col = dest % width;
+            }
+            if (flip & 2)
+                row = height - 1 - row;
+            if (flip & 1)
+                col = width - 1 - col;
+            next = row * width + col;
+            if (next == base) break;
+            flag[next >> 5] |= 1 << (next & 31);
+            img[dest] = img[next];
+            dest = next;
+        }
+        img[dest] = hold;
+    }
+    free (flag);
+    if (flip & 4) {
+        temp = height;
+        height = width;
+        width = temp;
+        temp = ymag;
+        ymag = xmag;
+        xmag = temp;
+    }
+}
+
+/*
+   Write the image as an RGB PAM image
+ */
+static void CLASS write_pam_nonlinear (FILE *ofp)
+{
+  unsigned char lut[0x10000];
+  int perc, c, val, total, i, row, col;
+  float white=0, r;
+  struct pam pam;
+  tuple * tuplerow;
+
+  pam.size   = sizeof(pam);
+  pam.len    = PAM_STRUCT_SIZE(tuple_type);
+  pam.file   = ofp;
+  pam.width  = xmag*(width-trim*2);
+  pam.height = ymag*(height-trim*2);
+  pam.depth  = 3;
+  pam.format = PAM_FORMAT;
+  pam.maxval = 255;
+  strcpy(pam.tuple_type, "RGB");
+
+  pnm_writepaminit(&pam);
+
+  tuplerow = pnm_allocpamrow(&pam);
+
+  perc = width * height * 0.01;     /* 99th percentile white point */
+  if (fuji_width) perc /= 2;
+  FORC3 {
+    for (val=0x2000, total=0; --val > 32; )
+      if ((total += histogram[c][val]) > perc) break;
+    if (white < val) white = val;
+  }
+  white *= 8 / cmdline.bright;
+  for (i=0; i < 0x10000; i++) {
+    r = i / white;
+    val = 256 * ( !use_gamma ? r :
+    r <= 0.018 ? r*4.5 : pow(r,0.45)*1.099-0.099 );
+    if (val > 255) val = 255;
+    lut[i] = val;
+  }
+  for (row=trim; row < height-trim; row++) {
+      for (col=trim; col < width-trim; col++) {
+          unsigned int plane;
+          for (plane=0; plane < pam.depth; ++plane) {
+              unsigned int copy;
+              for (copy=0; copy < xmag; ++copy) {
+                  unsigned int const pamcol = xmag*(col-trim)+copy;
+                  tuplerow[pamcol][plane] = lut[image[row*width+col][plane]];
+              }
+          }
+      }
+      {
+          unsigned int copy;
+          for (copy=0; copy < ymag; ++copy)
+              pnm_writepamrow(&pam, tuplerow);
+      }
+  }
+  pnm_freepamrow(tuplerow);
+}
+
+/*
+   Write the image to a 16-bit PAM file with linear color space
+ */
+static void CLASS write_pam_linear (FILE *ofp)
+{
+  int row;
+
+  struct pam pam;
+  tuple * tuplerow;
+
+  if (maximum < 256) maximum = 256;
+
+  pam.size   = sizeof(pam);
+  pam.len    = PAM_STRUCT_SIZE(tuple_type);
+  pam.file   = ofp;
+  pam.width  = width-trim*2;
+  pam.height = height-trim*2;
+  pam.depth  = 3;
+  pam.format = PAM_FORMAT;
+  pam.maxval = MAX(maximum, 256);
+  strcpy(pam.tuple_type, "RGB");
+
+  pnm_writepaminit(&pam);
+
+  tuplerow = pnm_allocpamrow(&pam);
+
+  for (row = trim; row < height-trim; row++) {
+      unsigned int col;
+      for (col = trim; col < width-trim; col++) {
+          unsigned int const pamCol = col - trim;
+          unsigned int plane;
+          for (plane = 0; plane < 3; ++plane)
+              tuplerow[pamCol][plane] = image[row*width+col][plane];
+      }
+      pnm_writepamrow(&pam, tuplerow);
+  }
+  pnm_freepamrow(tuplerow);
+}
+
+
+
+static void CLASS
+writePam(FILE * const ofP,
+         bool   const linear) {
+
+    if (linear)
+        write_pam_linear(ofP);
+    else
+        write_pam_nonlinear(ofP);
+}
+
+
+
+
+static void CLASS
+convertIt(FILE *    const ifP,
+          FILE *    const ofP,
+          loadRawFn const load_raw) {
+
+    shrink = cmdline.half_size && filters;
+    iheight = (height + shrink) >> shrink;
+    iwidth  = (width  + shrink) >> shrink;
+    image = calloc (iheight*iwidth*sizeof *image + meta_length, 1);
+    merror (image, "main()");
+    meta_data = (char *) (image + iheight*iwidth);
+    if (cmdline.verbose)
+        pm_message ("Loading %s %s image ...", make, model);
+
+    use_secondary = cmdline.use_secondary;  /* for load_raw() */
+
+    ifp = ifP;  /* Set global variable for (*load_raw)() */
+
+    load_raw();
+    bad_pixels();
+    height = iheight;
+    width  = iwidth;
+    if (is_foveon) {
+        if (cmdline.verbose)
+            pm_message ("Foveon interpolation...");
+        foveon_interpolate(coeff);
+    } else {
+        scale_colors();
+    }
+    if (shrink) filters = 0;
+    trim = 0;
+    if (filters && !cmdline.document_mode) {
+        trim = 1;
+        if (cmdline.verbose)
+            pm_message ("%s interpolation...",
+                        cmdline.quick_interpolate ? "Bilinear":"VNG");
+        vng_interpolate();
+    }
+    fuji_rotate();
+    apply_profile(ifP, cmdline.profile);
+    if (cmdline.verbose)
+        pm_message ("Converting to RGB colorspace...");
+    convert_to_rgb();
+
+    if (flip) {
+        if (cmdline.verbose)
+            pm_message ("Flipping image %c:%c:%c...",
+                        flip & 1 ? 'H':'0', flip & 2 ? 'V':'0', 
+                        flip & 4 ? 'T':'0');
+        flip_image();
+    }
+    writePam(ofP, cmdline.linear);
+}
+
+
+int 
+main (int argc, char **argv) {
+
+    FILE * const ofP = stdout;
+
+    FILE * ifP;
+    int rc;
+    loadRawFn load_raw;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    verbose = cmdline.verbose;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    image = NULL;
+    
+    rc = identify(ifP,
+                  cmdline.use_secondary, cmdline.use_camera_rgb,
+                  cmdline.red_scale, cmdline.blue_scale,
+                  cmdline.four_color_rgb, cmdline.inputFileName,
+                  &load_raw);
+    if (rc != 0)
+        pm_error("Unable to identify the format of the input image");
+    else {
+        if (cmdline.identify_only) {
+            pm_message ("Input is a %s %s image.", make, model);
+        } else {
+            if (cmdline.verbose)
+                pm_message ("Input is a %s %s image.", make, model);
+            convertIt(ifP, ofP, load_raw);
+        }
+    }
+    pm_close(ifP);
+    pm_close(ofP);
+    free(image);
+
+    return 0;
+}
+
+
+