//
//  Little cms
//  Copyright (C) 1998-2007 Marti Maria
//
// Permission is hereby granted, free of charge, to any person obtaining 
// a copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software 
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


// This program does apply profiles to (some) JPEG files


#include "lcms.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/stat.h>

#ifndef NON_WINDOWS
#    include <io.h>
#endif

#include "jpeglib.h"
#include "iccjpeg.h"

#define PROGNAME    "JPEGICC"

// xgetopt() interface -----------------------------------------------------

extern int   xoptind;
extern char *xoptarg;
extern int   xopterr;
extern char   SW;
int    cdecl xgetopt(int argc, char *argv[], char *optionS);

// ------------------------------------------------------------------------

extern cmsHPROFILE OpenStockProfile(const char* File);


// Flags

static LCMSBOOL Verbose                = FALSE;
static LCMSBOOL BlackPointCompensation = FALSE;
static LCMSBOOL IgnoreEmbedded         = FALSE;
static LCMSBOOL GamutCheck             = FALSE;
static LCMSBOOL lIsITUFax              = FALSE;
static LCMSBOOL lIsPhotoshopApp13      = FALSE;
static LCMSBOOL lIsDeviceLink          = FALSE;
static LCMSBOOL EmbedProfile           = FALSE;
static int PreserveBlack		   = 0;

static const char* SaveEmbedded = NULL;

static int Intent                  = INTENT_PERCEPTUAL;
static int ProofingIntent          = INTENT_PERCEPTUAL;
static int PrecalcMode             = 1;

static int jpegQuality             = 75;

static double ObserverAdaptationState = 0;

static char *cInpProf  = NULL;
static char *cOutProf  = NULL;
static char *cProofing = NULL;

static FILE * InFile;
static FILE * OutFile;

static struct jpeg_decompress_struct Decompressor;
static struct jpeg_compress_struct   Compressor;


static struct my_error_mgr {

    struct  jpeg_error_mgr pub;  // "public" fields
    LPVOID  Cargo;               // "private" fields

} ErrorHandler;

static
void ConsoleWarningHandler(const char* module, const char* fmt, va_list ap)
{
    char e[512] = { '\0' };
    if (module != NULL)
        strcat(strcpy(e, module), ": ");
    
    vsprintf(e+strlen(e), fmt, ap);
    strcat(e, ".");
    if (Verbose) {
        
        fprintf(stderr, "\nWarning");
        fprintf(stderr, " %s\n", e);
    }
}

static
void ConsoleErrorHandler(const char* module, const char* fmt, va_list ap)
{
       char e[512] = { '\0' };

       if (module != NULL)
              strcat(strcpy(e, module), ": ");

       vsprintf(e+strlen(e), fmt, ap);
       strcat(e, ".");
       fprintf(stderr, "\nError");
       fprintf(stderr, " %s\n", e);
}

// Force an error and exit w/ return code 1

static
void FatalError(const char *frm, ...)
{
       va_list args;

       va_start(args, frm);
       ConsoleErrorHandler(PROGNAME, frm, args);
       va_end(args);
       exit(1);
}

static
int MyErrorHandler(int ErrorCode, const char *ErrorText)
{
    FatalError("%s", ErrorText);
    return 0;
}


// Out of mem
static
void OutOfMem(size_t size)
{
    FatalError("Out of memory on allocating %d bytes.", size);
}


METHODDEF(void)
my_output_message (j_common_ptr cinfo)
{
  char buffer[JMSG_LENGTH_MAX];

  (*cinfo->err->format_message) (cinfo, buffer);
  FatalError("%s %s", PROGNAME, buffer);  
}


METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  // struct my_error_mgr *myerr = (struct my_error_mgr *) cinfo->err;
  char buffer[JMSG_LENGTH_MAX];
  
  (*cinfo->err->format_message) (cinfo, buffer);
  FatalError(buffer);
}



static
LCMSBOOL IsITUFax(jpeg_saved_marker_ptr ptr)
{
    while (ptr) {
        
        if (ptr -> marker == (JPEG_APP0 + 1) && ptr -> data_length > 5)
        {
            JOCTET FAR* data = ptr -> data;
            
            if (GETJOCTET(data[0]) == 0x47 &&
                GETJOCTET(data[1]) == 0x33 &&
                GETJOCTET(data[2]) == 0x46 &&
                GETJOCTET(data[3]) == 0x41 &&
                GETJOCTET(data[4]) == 0x58)
                return TRUE;
        }
        
        ptr = ptr -> next;
    }

   return FALSE;

}

#define PS_FIXED_TO_FLOAT(h, l) ((float) (h) + ((float) (l)/(1<<16)))

static
LCMSBOOL ProcessPhotoshopAPP13(JOCTET FAR *data, int datalen)
{
    int i;

    for (i = 14; i < datalen; )
    {
        long len;
        unsigned int type;
                
        if (!(GETJOCTET(data[i]  ) == 0x38 &&
              GETJOCTET(data[i+1]) == 0x42 &&
              GETJOCTET(data[i+2]) == 0x49 &&
              GETJOCTET(data[i+3]) == 0x4D)) break; // Not recognized
                    
        i += 4; // identifying string 
        
        type = (unsigned int) (GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]));

        i += 2; // resource type 
        
        i += GETJOCTET(data[i]) + ((GETJOCTET(data[i]) & 1) ? 1 : 2);   // resource name 
        
        len = ((((GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]))<<8) + 
                         GETJOCTET(data[i+2]))<<8) + GETJOCTET(data[i+3]);

        i += 4; // Size 
        
        if (type == 0x03ED && len >= 16) {
            
            Decompressor.X_density = (int) PS_FIXED_TO_FLOAT(GETJOCTET(data[i]<<8) + GETJOCTET(data[i+1]),
                                                 GETJOCTET(data[i+2]<<8) + GETJOCTET(data[i+3]));
            Decompressor.Y_density = (int) PS_FIXED_TO_FLOAT(GETJOCTET(data[i+8]<<8) + GETJOCTET(data[i+9]),
                                                 GETJOCTET(data[i+10]<<8) + GETJOCTET(data[i+11]));
            
            // Set the density unit to 1 since the 
            // Vertical and Horizontal resolutions 
            // are specified in Pixels per inch 

            Decompressor.density_unit = 0x01;
            return TRUE;
            
        }
                
        i += len + ((len & 1) ? 1 : 0);   // Alignment             
    }
    return FALSE;
}


static
LCMSBOOL HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
{
    while (ptr) {
        
        if (ptr -> marker == (JPEG_APP0 + 13) && ptr -> data_length > 9)
        {    
            JOCTET FAR* data = ptr -> data;    
            
            if(GETJOCTET(data[0]) == 0x50 &&
               GETJOCTET(data[1]) == 0x68 &&
               GETJOCTET(data[2]) == 0x6F &&
               GETJOCTET(data[3]) == 0x74 &&
               GETJOCTET(data[4]) == 0x6F &&
               GETJOCTET(data[5]) == 0x73 &&
               GETJOCTET(data[6]) == 0x68 &&
               GETJOCTET(data[7]) == 0x6F &&
               GETJOCTET(data[8]) == 0x70) {
             
                ProcessPhotoshopAPP13(data, ptr -> data_length);
                return TRUE;
            }
        }
        
        ptr = ptr -> next;
    }
    
    return FALSE;    
}



static
LCMSBOOL OpenInput(const char* FileName)
{
    int m;
    
    lIsITUFax = FALSE;   
    InFile  = fopen(FileName, "rb");
    if (InFile == NULL) {
        FatalError("Cannot open '%s'", FileName);
        return FALSE;
    }
    
    // Now we can initialize the JPEG decompression object.
    
    Decompressor.err                 = jpeg_std_error(&ErrorHandler.pub);
    ErrorHandler.pub.error_exit      = my_error_exit;
    ErrorHandler.pub.output_message  = my_output_message;
    
    jpeg_create_decompress(&Decompressor);
    jpeg_stdio_src(&Decompressor, InFile);
    
    for (m = 0; m < 16; m++)
        jpeg_save_markers(&Decompressor, JPEG_APP0 + m, 0xFFFF);
    
    setup_read_icc_profile(&Decompressor);  
    
    fseek(InFile, 0, SEEK_SET);
    jpeg_read_header(&Decompressor, TRUE);
    
    return TRUE;
}


static
LCMSBOOL OpenOutput(const char* FileName)
{

        OutFile = fopen(FileName, "wb");
        if (OutFile == NULL) {
                    FatalError("Cannot create '%s'", FileName);
                    return FALSE;
        }

        Compressor.err                   = jpeg_std_error(&ErrorHandler.pub);
        ErrorHandler.pub.error_exit      = my_error_exit;
        ErrorHandler.pub.output_message  = my_output_message;

        Compressor.input_components = Compressor.num_components = 4;
        
        jpeg_create_compress(&Compressor);
        jpeg_stdio_dest(&Compressor, OutFile);  
        
        return TRUE;
}

static
LCMSBOOL Done()
{
       jpeg_destroy_decompress(&Decompressor);
       jpeg_destroy_compress(&Compressor);
       return fclose(InFile) + fclose(OutFile);
       
}


// Build up the pixeltype descriptor

static
DWORD GetInputPixelType(void)
{
     int space, bps, extra, ColorChannels, Flavor;
        
   
     lIsITUFax         = IsITUFax(Decompressor.marker_list);
     lIsPhotoshopApp13 = HandlePhotoshopAPP13(Decompressor.marker_list);

     ColorChannels = Decompressor.num_components;
     extra  = 0;            // Alpha = None
     bps    = 1;            // 8 bits
     Flavor = 0;            // Vanilla

     if (lIsITUFax) {

        space = PT_Lab;
        Decompressor.out_color_space = JCS_YCbCr;  // Fake to don't touch
     }
     else
     switch (Decompressor.jpeg_color_space) {

     case JCS_GRAYSCALE:        // monochrome
              space = PT_GRAY;
              Decompressor.out_color_space = JCS_GRAYSCALE;
              break;

     case JCS_RGB:             // red/green/blue
              space = PT_RGB;
              Decompressor.out_color_space = JCS_RGB;
              break;

     case JCS_YCbCr:               // Y/Cb/Cr (also known as YUV)
              space = PT_RGB;      // Let IJG code to do the conversion
              Decompressor.out_color_space = JCS_RGB;   
              break;

     case JCS_CMYK:            // C/M/Y/K
              space = PT_CMYK;
              Decompressor.out_color_space = JCS_CMYK;
              if (Decompressor.saw_Adobe_marker)            // Adobe keeps CMYK inverted, so change flavor
                                Flavor = 1;                 // from vanilla to chocolate
              break;

     case JCS_YCCK:            // Y/Cb/Cr/K              
              space = PT_CMYK;
              Decompressor.out_color_space = JCS_CMYK;
              if (Decompressor.saw_Adobe_marker)            // ditto
                                Flavor = 1;                 
              break;

     default:
              FatalError("Unsupported color space (0x%x)", Decompressor.jpeg_color_space);
              return 0;
     }

     return (EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|COLORSPACE_SH(space)|FLAVOR_SH(Flavor));
}





// Rearrange pixel type to build output descriptor

static
DWORD ComputeOutputFormatDescriptor(DWORD dwInput, int OutColorSpace)
{
   int IsPlanar  = T_PLANAR(dwInput);
   int Channels  = 0;
   int Flavor    = 0;

   switch (OutColorSpace) {

   case PT_GRAY:
               Channels = 1;
               break;
   case PT_RGB:
   case PT_CMY:
   case PT_Lab:
   case PT_YUV:
   case PT_YCbCr:
               Channels = 3;
               break;

   case PT_CMYK:
               if (Compressor.write_Adobe_marker)   // Adobe keeps CMYK inverted, so change flavor to chocolate
                        Flavor = 1;

               Channels = 4;
               break;
   default:
               FatalError("Unsupported output color space");
   }

    return (COLORSPACE_SH(OutColorSpace)|PLANAR_SH(IsPlanar)|CHANNELS_SH(Channels)|BYTES_SH(1)|FLAVOR_SH(Flavor));
}


// Equivalence between ICC color spaces and lcms color spaces

static
int GetProfileColorSpace(cmsHPROFILE hProfile)
{
    icColorSpaceSignature ProfileSpace = cmsGetColorSpace(hProfile);

       switch (ProfileSpace) {

       case icSigGrayData: return  PT_GRAY;
       case icSigRgbData:  return  PT_RGB;
       case icSigCmyData:  return  PT_CMY;
       case icSigCmykData: return  PT_CMYK;
       case icSigYCbCrData:return  PT_YCbCr;
       case icSigLuvData:  return  PT_YUV;
       case icSigXYZData:  return  PT_XYZ;
       case icSigLabData:  return  PT_Lab;
       case icSigLuvKData: return  PT_YUVK;
       case icSigHsvData:  return  PT_HSV;
       case icSigHlsData:  return  PT_HLS;
       case icSigYxyData:  return  PT_Yxy;

       case icSigHexachromeData: return PT_HiFi;


       default:  return icMaxEnumData;
       }
}

// From TRANSUPP

static 
void jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo)
{
  jpeg_saved_marker_ptr marker;

  /* In the current implementation, we don't actually need to examine the
   * option flag here; we just copy everything that got saved.
   * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
   * if the encoder library already wrote one.
   */
  for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
    if (dstinfo->write_JFIF_header &&
    marker->marker == JPEG_APP0 &&
    marker->data_length >= 5 &&
    GETJOCTET(marker->data[0]) == 0x4A &&
    GETJOCTET(marker->data[1]) == 0x46 &&
    GETJOCTET(marker->data[2]) == 0x49 &&
    GETJOCTET(marker->data[3]) == 0x46 &&
    GETJOCTET(marker->data[4]) == 0)
      continue;         /* reject duplicate JFIF */

    if (dstinfo->write_Adobe_marker &&
    marker->marker == JPEG_APP0+14 &&
    marker->data_length >= 5 &&
    GETJOCTET(marker->data[0]) == 0x41 &&
    GETJOCTET(marker->data[1]) == 0x64 &&
    GETJOCTET(marker->data[2]) == 0x6F &&
    GETJOCTET(marker->data[3]) == 0x62 &&
    GETJOCTET(marker->data[4]) == 0x65)
      continue;         /* reject duplicate Adobe */

#ifdef NEED_FAR_POINTERS
    /* We could use jpeg_write_marker if the data weren't FAR... */
    {
      unsigned int i;

      jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
      for (i = 0; i < marker->data_length; i++)
        jpeg_write_m_byte(dstinfo, marker->data[i]);
    }
#else
    jpeg_write_marker(dstinfo, marker->marker,
              marker->data, marker->data_length);
#endif
  }
}

static
void WriteOutputFields(int OutputColorSpace)
{
    J_COLOR_SPACE in_space, jpeg_space;
    int components;
    
    switch (OutputColorSpace) {
        
    case PT_GRAY: in_space = jpeg_space = JCS_GRAYSCALE; 
                  components = 1; 
                  break;

    case PT_RGB:  in_space = JCS_RGB; 
                  jpeg_space = JCS_YCbCr; 
                  components = 3; 
                  break;       // red/green/blue

    case PT_YCbCr: in_space = jpeg_space = JCS_YCbCr; 
                   components = 3; 
                   break;               // Y/Cb/Cr (also known as YUV)

    case PT_CMYK: in_space = JCS_CMYK; 
                  jpeg_space = JCS_YCCK;  
                  components = 4; 
                  break;      // C/M/Y/components              

    case PT_Lab:  in_space = jpeg_space = JCS_YCbCr; 
                  components = 3; 
                  break;                // Fake to don't touch
    default:
                FatalError("Unsupported output color space");
                return;
    }
    

    if (jpegQuality >= 100) {

     // avoid destructive conversion when asking for lossless compression 
        jpeg_space = in_space;
    }

    Compressor.in_color_space =  in_space;
    Compressor.jpeg_color_space = jpeg_space;
    Compressor.input_components = Compressor.num_components = components;
    jpeg_set_defaults(&Compressor);
    jpeg_set_colorspace(&Compressor, jpeg_space);

    // Make sure to pass resolution through
    if (OutputColorSpace == PT_CMYK)
        Compressor.write_JFIF_header = 1;

    //avoid subsampling on high quality factor
    jpeg_set_quality(&Compressor, jpegQuality, 1);    
    if (jpegQuality >= 70) {

      int i;
      for(i=0; i < Compressor.num_components; i++) {

	        Compressor.comp_info[i].h_samp_factor = 1;
            Compressor.comp_info[i].v_samp_factor = 1;
      }

    }

}

// A replacement for (the nonstandard) filelenght

static
int xfilelength(int fd)
{
#ifdef _MSC_VER
        return _filelength(fd);
#else
        struct stat sb;
        if (fstat(fd, &sb) < 0)
                return(-1);
        return(sb.st_size);
#endif


}

static
void DoEmbedProfile(const char* ProfileFile)
{
    FILE* f;
    size_t size, EmbedLen;
    LPBYTE EmbedBuffer;

        f = fopen(ProfileFile, "rb");
        if (f == NULL) return;

        size = xfilelength(fileno(f));
        EmbedBuffer = (LPBYTE) _cmsMalloc(size + 1);
        EmbedLen = fread(EmbedBuffer, 1, size, f);
        fclose(f);
        EmbedBuffer[EmbedLen] = 0;

        write_icc_profile (&Compressor, EmbedBuffer, EmbedLen);		   
        _cmsFree(EmbedBuffer);
}



static
int DoTransform(cmsHTRANSFORM hXForm)
{       
    JSAMPROW ScanLineIn;
    JSAMPROW ScanLineOut;

    
       //Preserve resolution values from the original
       // (Thanks to robert bergs for finding out this bug)

       Compressor.density_unit = Decompressor.density_unit;
       Compressor.X_density    = Decompressor.X_density;
       Compressor.Y_density    = Decompressor.Y_density;

      //  Compressor.write_JFIF_header = 1;
     
       jpeg_start_decompress(&Decompressor);
       jpeg_start_compress(&Compressor, TRUE);

       // Embed the profile if needed
       if (EmbedProfile && cOutProf) 
           DoEmbedProfile(cOutProf);

       ScanLineIn  = (JSAMPROW) _cmsMalloc(Decompressor.output_width * Decompressor.num_components);
       ScanLineOut = (JSAMPROW) _cmsMalloc(Compressor.image_width * Compressor.num_components);

       while (Decompressor.output_scanline <
                            Decompressor.output_height) {

       jpeg_read_scanlines(&Decompressor, &ScanLineIn, 1);

       cmsDoTransform(hXForm, ScanLineIn, ScanLineOut, Decompressor.output_width);

       jpeg_write_scanlines(&Compressor, &ScanLineOut, 1);

       }

       _cmsFree(ScanLineIn); 
       _cmsFree(ScanLineOut);

       jpeg_finish_decompress(&Decompressor);
       jpeg_finish_compress(&Compressor);
       
       return TRUE;
}




static
void SaveMemoryBlock(const BYTE* Buffer, DWORD dwLen, const char* Filename)
{
    FILE* out = fopen(Filename, "wb");
    if (out == NULL)
           FatalError("Cannot create '%s'", Filename);

    if (fwrite(Buffer, 1, dwLen, out) != dwLen)
            FatalError("Cannot write %ld bytes to %s", dwLen, Filename);

    if (fclose(out) != 0) 
            FatalError("Error flushing file '%s'", Filename);
}



// Transform one image

static
int TransformImage(char *cDefInpProf, char *cOutProf)
{
       cmsHPROFILE hIn, hOut, hProof;
       cmsHTRANSFORM xform;
       DWORD wInput, wOutput;
       int OutputColorSpace;
       DWORD dwFlags = 0; 
       DWORD EmbedLen;
       LPBYTE EmbedBuffer;

	   // Observer adaptation state (only meaningful on absolute colorimetric intent)

       cmsSetAdaptationState(ObserverAdaptationState);


       if (BlackPointCompensation) {

            dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;            
       }


       if (PreserveBlack) {
			dwFlags |= cmsFLAGS_PRESERVEBLACK;
			if (PrecalcMode == 0) PrecalcMode = 1;
            cmsSetCMYKPreservationStrategy(PreserveBlack-1);
	   }


       switch (PrecalcMode) {
           
       case 0: dwFlags |= cmsFLAGS_NOTPRECALC; break;
       case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
       case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
       default:;
       }
        

       if (GamutCheck)
            dwFlags |= cmsFLAGS_GAMUTCHECK;
        

        if (lIsDeviceLink) {

            hIn = cmsOpenProfileFromFile(cDefInpProf, "r");
            hOut = NULL;
            hProof = NULL;
       }
        else {

        if (!IgnoreEmbedded && read_icc_profile(&Decompressor, &EmbedBuffer, &EmbedLen))
        {
              hIn = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);

               if (Verbose) {

                  fprintf(stdout, " (Embedded profile found)\n");
                  fprintf(stdout, "Product name: %s\n", cmsTakeProductName(hIn));
                  fprintf(stdout, "Description : %s\n", cmsTakeProductDesc(hIn));                          
                  fflush(stdout);
              }

               if (hIn != NULL && SaveEmbedded != NULL)
                          SaveMemoryBlock(EmbedBuffer, EmbedLen, SaveEmbedded);

              _cmsFree(EmbedBuffer);
        }
        else
        {
                hIn = OpenStockProfile(cDefInpProf);
       }

        hOut = OpenStockProfile(cOutProf);


       hProof = NULL;
       if (cProofing != NULL) {

           hProof = OpenStockProfile(cProofing);
           dwFlags |= cmsFLAGS_SOFTPROOFING;
          }
       }

       // Take input color space

       wInput = GetInputPixelType();

       // Assure both, input profile and input JPEG are on same colorspace

       
       if (cmsGetColorSpace(hIn) != _cmsICCcolorSpace(T_COLORSPACE(wInput)))
              FatalError("Input profile is not operating in proper color space");
       

       // Output colorspace is given by output profile

        if (lIsDeviceLink) {
            OutputColorSpace = T_COLORSPACE(wInput);
        }
        else {
            OutputColorSpace = GetProfileColorSpace(hOut);
        }

       jpeg_copy_critical_parameters(&Decompressor, &Compressor);
       
       WriteOutputFields(OutputColorSpace);               
       
       wOutput      = ComputeOutputFormatDescriptor(wInput, OutputColorSpace);
       
       xform = cmsCreateProofingTransform(hIn, wInput, 
                                          hOut, wOutput, 
                                          hProof, Intent, 
                                          ProofingIntent, dwFlags);

       // Handle tile by tile or strip by strip strtok

       DoTransform(xform);

       
       jcopy_markers_execute(&Decompressor, &Compressor);
       
       cmsDeleteTransform(xform);
       cmsCloseProfile(hIn);
       cmsCloseProfile(hOut);
       if (hProof) cmsCloseProfile(hProof);
       
       return 1;
}


// Simply print help

static
void Help(int level)
{
     fprintf(stderr, "little cms ICC profile applier for JPEG - v2.1\n\n");

     switch(level) {

     default:
     case 0:

     fprintf(stderr, "usage: jpegicc [flags] input.jpg output.jpg\n");

     fprintf(stderr, "\nflags:\n\n");
     fprintf(stderr, "%cv - Verbose\n", SW);
     fprintf(stderr, "%ci<profile> - Input profile (defaults to sRGB)\n", SW);
     fprintf(stderr, "%co<profile> - Output profile (defaults to sRGB)\n", SW);   
     fprintf(stderr, "%ct<0,1,2,3> - Intent (0=Perceptual, 1=Colorimetric, 2=Saturation, 3=Absolute)\n", SW);    
     
     fprintf(stderr, "\n");

     fprintf(stderr, "%cb - Black point compensation\n", SW);
     fprintf(stderr, "%cf<n> - Preserve black (CMYK only) 0=off, 1=black ink only, 2=full K plane\n", SW);
     fprintf(stderr, "%cn - Ignore embedded profile\n", SW);
     fprintf(stderr, "%ce - Embed destination profile\n", SW);
     fprintf(stderr, "%cs<new profile> - Save embedded profile as <new profile>\n", SW);

     fprintf(stderr, "\n");

     fprintf(stderr, "%cc<0,1,2,3> - Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes) [defaults to 1]\n", SW);     
     fprintf(stderr, "\n");

     fprintf(stderr, "%cp<profile> - Soft proof profile\n", SW);
     fprintf(stderr, "%cm<0,1,2,3> - SoftProof intent\n", SW);
     fprintf(stderr, "%cg - Marks out-of-gamut colors on softproof\n", SW);

     fprintf(stderr, "\n");
     fprintf(stderr, "%cq<0..100> - Output JPEG quality\n", SW);
	 fprintf(stderr, "\n");
	 fprintf(stderr, "%cd<0..1> - Observer adaptation state (abs.col. only)\n", SW);

     fprintf(stderr, "\n");
     fprintf(stderr, "%ch<0,1,2> - More help\n", SW);
     break;

     case 1:

     
     fprintf(stderr, "Examples:\n\n"
                     "To color correct from scanner to sRGB:\n"
                     "\tjpegicc %ciscanner.icm in.jpg out.jpg\n"
                     "To convert from monitor1 to monitor2:\n"
                     "\tjpegicc %cimon1.icm %comon2.icm in.jpg out.jpg\n"
                     "To make a CMYK separation:\n"
                     "\tjpegicc %coprinter.icm inrgb.jpg outcmyk.jpg\n"
                     "To recover sRGB from a CMYK separation:\n"
                     "\tjpegicc %ciprinter.icm incmyk.jpg outrgb.jpg\n"
                     "To convert from CIELab ITU/Fax JPEG to sRGB\n"
                     "\tjpegicc %ciitufax.icm in.jpg out.jpg\n\n", 
                     SW, SW, SW, SW, SW, SW);
     break;

     case 2:

     fprintf(stderr, "This program is intended to be a demo of the little cms\n"
                     "engine. Both lcms and this program are freeware. You can\n"
                     "obtain both in source code at http://www.littlecms.com\n"
                     "For suggestions, comments, bug reports etc. send mail to\n"
                     "marti@littlecms.com\n\n");
     break;
     }

     exit(0);
}


// The toggles stuff

static
void HandleSwitches(int argc, char *argv[])
{
    int s;
    
	while ((s=xgetopt(argc,argv,"bBnNvVGgh:H:i:I:o:O:P:p:t:T:c:C:Q:q:M:m:L:l:eEs:S:F:f:D:d:")) != EOF) {
        
        switch (s)
        {
            
        case 'b':
        case 'B':
            BlackPointCompensation = TRUE;
            break;
            
        case 'v':
        case 'V':
            Verbose = TRUE;
            break;
            
        case 'i':
        case 'I':
            
            if (lIsDeviceLink)
                FatalError("Device-link already specified"); 
            
            cInpProf = xoptarg;
            break;
            
        case 'o':
        case 'O':
            
            if (lIsDeviceLink)
                FatalError("Device-link already specified"); 
            
            cOutProf = xoptarg;
            break;
            
        case 'l':
        case 'L': 
            cInpProf = xoptarg;
            lIsDeviceLink = TRUE;
            break;
            
        case 'p':
        case 'P':
            cProofing = xoptarg;
            break;
            
        case 't':
        case 'T':
            Intent = atoi(xoptarg);
            if (Intent > 3) Intent = 3;
            if (Intent < 0) Intent = 0;
            break;
            
        case 'N':
        case 'n':
            IgnoreEmbedded = TRUE;
            break;
            
        case 'e':
        case 'E':
            EmbedProfile = TRUE;
            break;
            
       case 'f':
	   case 'F':
		    PreserveBlack = atoi(xoptarg);
            if (PreserveBlack < 0 || PreserveBlack > 2)
                    FatalError("Unknown PreserveBlack '%d'", PreserveBlack);
			break;

        case 'g':
        case 'G':
            GamutCheck = TRUE;
            break;
            
        case 'c':
        case 'C':
            PrecalcMode = atoi(xoptarg);
            if (PrecalcMode < 0 || PrecalcMode > 2)
                FatalError("Unknown precalc mode '%d'", PrecalcMode);
            break;
            
        case 'H':
        case 'h':  {
            
            int a =  atoi(xoptarg);
            Help(a); 
                   }
            break;
            
        case 'q':
        case 'Q':
            jpegQuality = atoi(xoptarg);
            if (jpegQuality > 100) jpegQuality = 100;
            if (jpegQuality < 0)   jpegQuality = 0;
            break;
            
        case 'm':
        case 'M':
            ProofingIntent = atoi(xoptarg);
            if (ProofingIntent > 3) ProofingIntent = 3;
            if (ProofingIntent < 0) ProofingIntent = 0;
            break;
            
        case 's':
        case 'S': SaveEmbedded = xoptarg;
            break;
            
        case 'd':
        case 'D': ObserverAdaptationState = atof(xoptarg);
                 if (ObserverAdaptationState != 0 && 
                     ObserverAdaptationState != 1.0)
					 fprintf(stderr, "Warning: Adaptation states other that 0 or 1 are not yet implemented\n");
                 break;
            
        default:
            
            FatalError("Unknown option - run without args to see valid ones");
        }
        
    }
}


// The main sink

int main(int argc, char* argv[])
{
      char *Intents[] = {"perceptual",
                         "relative colorimetric",
                         "saturation",
                         "absolute colorimetric" };

      HandleSwitches(argc, argv);

      cmsSetErrorHandler(MyErrorHandler);

      if ((argc - xoptind) != 2) {

              Help(0);              
              }

     

      if (Verbose) {

          if (lIsDeviceLink)
            fprintf(stdout, "%s(device link) -> %s [%s]", 
                                                argv[xoptind], 
                                                argv[xoptind+1], 
                                                Intents[Intent]);

        else
            fprintf(stdout, "%s(%s) -> %s(%s) [%s]", argv[xoptind],
                                                (cInpProf == NULL ? "sRGB": cInpProf), 
                                                argv[xoptind+1],
                                                (cOutProf == NULL ? "sRGB" : cOutProf), 
                                                Intents[Intent]);
      }

      OpenInput(argv[xoptind]);
      OpenOutput(argv[xoptind+1]);
      TransformImage(cInpProf, cOutProf);


      if (Verbose) fprintf(stdout, "\n");

      Done();

      return 0;
}



