/*
 * This program is public domain
 *
 * kmprof2icc is s small program that be used to convert an 
 * official Konica-Minolta camera profile to a compliant ICC 
 * profile. 
 *
 * The Konica-Minolta profile are already ICC profiles but they
 * contain a non-standard intensity I curve (iTRC).
 *
 * What the program is doing is simply to apply the I curve to 
 * the standard Red, Green & Blue curves found in the profile.
 * The I curve is then converted to a linear curve. 
 *
 * DISCLAIMER 1: THIS IS ONLY FOR THE KONICA-MINOLTA DIGITAL 
 *               CAMERA PROFILES.
 * 
 * DISCLAIMER 2: I AM NOT A EXPERT IN ICC PROFILES SO I CANNOT 
 *               GUARANTY ANYTHING ABOUT THE QUALITY OF THE 
 *               RESULTING PROFILES.
 * 
 * Compilation:
 *   
 *   cc -o kmprof2icc  kmprof2icc.c
 *
 * Usage:
 * 
 *   kmprof2icc INPUT.icc OUTPUT.icc
 * 
 * Note: KM profiles often come in multiple versions for each camera. 
 *       Their naming scheme is:
 *
 *       XXXX_rprof.icc   : for MRW files (raw)
 *       XXXX_jprof.icc   : for JPEG files 
 *       XXXX_rz1prof.icc : for MRW files with HIGH Zone Matching (7D only. And 5D?)
 *       XXXX_rz2prof.icc : for MRW files with LOW Zone Matching (7D only. And 5D?)
 *  
 * Note: Do not ask me for the profiles. 
 *       I cannot redistribute them since they are (c) Konica-Minolta. 
 *       I cannot distribute the modified profiles either since their 
 *       are derived works from the original copyrighted files. 
 *
 * Note: The camera profiles (not for the 5D unfortunately) can be 
 *       obtained legally from the KM web site in the latest DiMAGE Viewer 
 *       software upgrade.
 *       If you are on Linux, the easiest way to get them is to 
 *       download the MAC version (dv237e.sit.hqx)  
 *       Then you need a program called unstuff that comes with stuffit.
 *       You can get the trial version from here
 *           http://www.stuffit.com/unix/index.html 
 *       or directly from here
 *           http://www.allume.com/downloads/files/stuffit520.611linux-i386.tar.gz
 *       Apply the program unstuff twice to the dv237e.sit.hqx and you will have your profiles.
 *   
 * For all remarks, questions, and bug reports, send me an email at:
 *   
 *    stephane at chauveau-central.net
 *   
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <assert.h>
#include <stdarg.h>

const char *infile ; 
const char *outfile ; 


char        * rawdata ; 
unsigned long rawsize ; 

static void 
fatal(char *fmt, ...) 
{
  va_list ap;
  fprintf(stderr, "ERROR: ") ;

  va_start(ap, fmt);
  vfprintf( stderr , fmt ,  ap ) ;
  va_end(ap);

  fprintf(stderr, "\n") ;
  exit(1) ; 
}

void
write_8(unsigned pos, unsigned long v) 
{
  assert(pos<rawsize) ;
  rawdata[pos] = v ;
} 

unsigned long
read_8(unsigned pos) 
{
  assert(pos<rawsize) ;
  return (unsigned char) rawdata[pos] ;
} 

void
write_16_m(unsigned pos, unsigned long v) 
{
  write_8(pos   , v >> 8) ; 
  write_8(pos+1 , v ) ;    
} 

unsigned long
read_16_m(unsigned pos) 
{
  return (read_8(pos)<<8) | read_8(pos+1) ;
} 

unsigned long 
read_32_m(unsigned pos) 
{ 
  return ( read_16_m(pos) << 16 ) | read_16_m(pos+2) ;
} 

int 
match4( unsigned long pos , const char *data ) 
{
  if ( data[0] == read_8(pos+0) ) {
    if ( data[1] == read_8(pos+1) ) {
      if ( data[2] == read_8(pos+2)) {
	if ( data[3] == read_8(pos+3) ) {
	  return 1 ; 
	}
      }
    }
  }
  return 0 ;
}


/*
 * Load the file into memory
 */
static void 
load_file(const char *fname) 
{
  FILE *f ;
  size_t sz ;

  f = fopen( fname , "rb" ) ; 

  if (f==NULL) {
    fatal("Failed to open file %s" , fname ) ;
  }

  fseek(f , 0 , SEEK_END) ;
  rawsize = ftell(f) ;
  fseek(f , 0 , SEEK_SET) ;

  if (rawsize>10000) {    
    fatal("This file looks too large (%lu)! KM camera profiles are usually around 4K" , rawsize ) ;
  }

  if (rawsize<2000) {    
    fatal("This file looks too small (%lu)! KM camera profiles are usually around 4K" , rawsize ) ;
  }

  rawdata = (char *) malloc( rawsize ) ; 

  printf("SIZE: %s %lu\n", fname ,(unsigned long) rawsize) ;

  if ( rawdata == NULL ) {
    fatal("Failed to allocate %lu bytes" , (unsigned long) rawsize ) ;
  }

  sz = fread( rawdata , 1 , rawsize , f) ;

  if (sz!=rawsize) {
    fatal("Failed to load %lu bytes from file %s. Only read %lu" , 
	  (unsigned long) rawsize , fname, (unsigned long) sz 
	  ) ;
  }
  
}

int 
main(int argc, char **argv)
{  
  FILE *out; 

  unsigned long pos ;

  unsigned long nbtag ;

  int k  ; 

  unsigned long rTRC_offset = 0 ;
  unsigned long rTRC_size   = 0 ;
  unsigned long gTRC_offset = 0 ;
  unsigned long gTRC_size   = 0 ;
  unsigned long bTRC_offset = 0 ;
  unsigned long bTRC_size   = 0 ;
  unsigned long iTRC_offset = 0 ;
  unsigned long iTRC_size   = 0 ;


  unsigned int  nb_samples ; 
  unsigned int *samples ; 

  if (argc!=3) {
    fprintf(stderr,"Usage: %s INPUT.icc OUTPUT.icc \n",argv[0]) ;
    fprintf(stderr,"\n") ; 
    fprintf(stderr," Convert the Konica-Minolta camera profile INPUT.icc\n") ; 
    fprintf(stderr," into an ICC compliant profile OUTPUT.icc \n") ; 
    fprintf(stderr,"\n") ; 
    exit(1) ; 
  }

  infile = argv[1] ; 
  outfile = argv[2] ; 

  load_file( infile) ;


  /* let's check a few entries in the header to be sure that we 
   * are processing a KM profile.
   */

  printf("== Looking for the acsp marker : "); 
  if (! match4( 36 , "acsp" ) ) 
    {
      printf("fail\n"); 
      fatal("ERROR: Profile signature 'acsp' not found\n" ) ;
      exit(1) ;   
    }
  printf("OK\n"); 
  
  printf("== Looking for the scnr marker : "); 
  if (! match4( 12 , "scnr" ) ) 
    {
      printf("fail\n"); 
      fatal("ERROR: Device class is not 'scnr'\n" ) ;
      exit(1) ; 
    }
  printf("OK\n"); 

  printf("== Looking for the KMHD or MNLT marker : "); 
  if (! ( match4( 48 , "KMHD" ) || match4( 48 , "MNLT" ) ) ) 
    {
      printf("fail\n"); 
      fatal("ERROR:  Manufacturer code is not 'KMHD' or 'MNLT')\n") ;
      exit(1) ; 
    }
  printf("OK\n"); 

  /* So now let's find the curve tags */

  pos = 128 ; 
  nbtag = read_32_m(pos) ;
  pos += 4 ;

  if ( nbtag<0 || nbtag>30) {
    fatal("ERROR: Unusual number of tags (%ld). I expect something around 10\n", nbtag ) ;
    exit(1) ; 
  }
  
  printf("== Found block of %d tags\n", (int) nbtag ) ;
  printf("== Searching for iTRC ...\n", (int) nbtag ) ;
  
  for (k=0;k<nbtag;k++) 
    {
      char sig[4];   
      unsigned long off  ; 
      unsigned long size ; 
      
      printf("   --> tag '%c%c%c%c' : ", 
	     (char) read_8(pos+0),  
	     (char) read_8(pos+1),  
	     (char) read_8(pos+2),  
	     (char) read_8(pos+3)
	     ) ;
      
      if ( match4( pos , "iTRC" ) )
	{
	  printf("yes\n"); 
	  iTRC_offset = read_32_m( pos+4 ) ;
	  iTRC_size   = read_32_m( pos+8 ) ;
	} 
      else if ( match4( pos , "rTRC" ) ) 
	{
	  printf("yes\n"); 
	  rTRC_offset = read_32_m( pos+4 ) ; ;
	  rTRC_size   = read_32_m( pos+8 )  ;
	} 
      else if ( match4( pos , "gTRC" ) ) 
	{
	  printf("yes\n"); 
	  gTRC_offset = read_32_m( pos+4 ) ; ;
	  gTRC_size   = read_32_m( pos+8 )  ;
	} 
      else if ( match4( pos , "bTRC" ) ) 
	{
	  printf("yes\n"); 
	  bTRC_offset = read_32_m( pos+4 ) ; ;
	  bTRC_size   = read_32_m( pos+8 )  ;
	} 
      else 
	{
	  printf("no\n"); 
	}

      pos += 12 ; 
  }

  if ( iTRC_offset==0 || iTRC_size==0 ) {
    fatal("ERROR: iTRC tag empty or not found\n", nbtag ) ;
  }
  
  if ( rTRC_offset==0 || rTRC_size==0 ) {
    fatal("ERROR: rTRC tag empty or not found\n", nbtag ) ;
  }
  
  if ( gTRC_offset==0 || gTRC_size==0 ) {
    fatal("ERROR: gTRC tag empty or not found\n", nbtag ) ;
  }

  if ( bTRC_offset==0 || bTRC_size==0 ) {
    fatal("ERROR: bTRC tag empty or not found\n", nbtag ) ;
  }

  printf( "rTRC size = %lu\n" , rTRC_size ); 
  printf( "gTRC size = %lu\n" , gTRC_size ); 
  printf( "bTRC size = %lu\n" , bTRC_size ); 
  printf( "iTRC size = %lu\n" , iTRC_size ); 
  

  if (iTRC_size != rTRC_size || iTRC_size != bTRC_size || iTRC_size != gTRC_size ) {
    fatal("ERROR: Inconsistant sizes\n") ;
  }

  /* the element count */
  nb_samples = read_32_m(iTRC_offset+8) ; 

#if 1
  /* All Cameras have exactly 12bit CCDs so 0..511 = 512 values 
   * That may change in the future!
   */
  if ( nb_samples != 512 ) {
    fatal("ERROR: Suspicious number of samples %ld (expect 512)\n", nb_samples) ;
  }
#endif 

  /* and the specified number of elements */
  assert(nb_samples < 1000000 ) ;

  iTRC_offset += 12 ; 
  rTRC_offset += 12 ; 
  gTRC_offset += 12 ; 
  bTRC_offset += 12 ; 
 
  for (k=1;k<nb_samples;k++) 
    {
      
      unsigned long r = read_16_m(rTRC_offset + 2*k)  ;
      unsigned long g = read_16_m(gTRC_offset + 2*k)  ;
      unsigned long b = read_16_m(bTRC_offset + 2*k)  ;
      unsigned long i = read_16_m(iTRC_offset + 2*k)  ;
      
      /* n is the linear value */ 
      unsigned long n = ( 0xFFFF*(k * 1.0) + (nb_samples-1)/2 ) / (nb_samples-1)  ;
      
      
#if 0
      printf("%3d: r=%04lX b=%04lX b=%04lX / i=%04lX / %d\n", k, r, g, b, i , n-b)  ;
#endif

      
      /* Apply the I curve to the RBG curves*/

#if 0
      /* without rounding */
      write_16_m(rTRC_offset + 2*k , (r*i)/n) ;
      write_16_m(gTRC_offset + 2*k , (g*i)/n) ;
      write_16_m(bTRC_offset + 2*k , (b*i)/n) ;
#else
      /* with rounding is probably more accurate */
      write_16_m(rTRC_offset + 2*k , (r*i+n/2)/n) ;
      write_16_m(gTRC_offset + 2*k , (g*i+n/2)/n) ;
      write_16_m(bTRC_offset + 2*k , (b*i+n/2)/n) ;
#endif


      /* And make the I curve linear so we can use our new profile 
       * in an application that can understand the iTRC tag.
       */
      write_16_m(iTRC_offset + 2*k , n) ;

  }

  /* Force the 1st and last elements to 0 and 0xFFFF to be compliant.
   * This is just to be sure there was no improper rounding
   */ 
  
  write_16_m(rTRC_offset + 2*0 , 0x0000) ;
  write_16_m(gTRC_offset + 2*0 , 0x0000) ;
  write_16_m(bTRC_offset + 2*0 , 0x0000) ;
  write_16_m(iTRC_offset + 2*0 , 0x0000) ;

  write_16_m(rTRC_offset + 2*(nb_samples-1) , 0xFFFF) ;
  write_16_m(gTRC_offset + 2*(nb_samples-1) , 0xFFFF) ;
  write_16_m(bTRC_offset + 2*(nb_samples-1) , 0xFFFF) ;
  write_16_m(iTRC_offset + 2*(nb_samples-1) , 0xFFFF) ;



  /* Good! let's write our new profile file */
  printf("writing the file '%s'\n", outfile) ;

  out = fopen(outfile,"rb") ; 
  if ( out != NULL ){
    fprintf(stderr,"ERROR: Sorry but the output file '%s' already exists. Please remove it\n", outfile ); 
    exit(1) ; 
  }

  out = fopen(outfile,"wb") ; 
  if ( out == NULL ){
    fprintf(stderr,"ERROR: Could not create the output file '%s'\n", outfile ); 
    exit(1) ; 
  }

  fwrite( rawdata , rawsize , 1 , out) ; 

  if ( ferror(out) ){
    fprintf(stderr,"ERROR: An error occured while creating the output file!\n"); 
    exit(1) ; 
  }
  

  fclose(out) ; 
  
  printf("done\n") ;

} 
