//
// Copyright (c) 1997,1998 Colosseum Builders, Inc.
// All rights reserved.
//
// Colosseum Builders, Inc. makes no warranty, expressed or implied
// with regards to this software. It is provided as is.
//
// See the README.TXT file that came with this software for restrictions
// on the use and redistribution of this file or send E-mail to
// info@colosseumbuilders.com
//

//
// JPEG Encoder Library.
//
// Title:   JpegEncoder Class Implementation
//
// Author: John M. Miano  miano@colosseumbuilders.com
//
// Modifications:
//
//   10-January-1999 Changed to use predefined quantization tables
//                   rather than tables scaled from the ones in the
//                   JPEG standard.
//
#include <math.h>
#include <string>
#include <cerrno>
#include "jpgencoder.h"
#include "bitmapimage.h"
#include "jfif.h"
#include "jpgencoderquantization.h"
#include "jpghuffmanencoder.h"       
#include "jpgoutputstream.h"  
#include "jpgoutputfilestream.h"
#include "jpgqtbls.h"

using namespace std ;
using namespace ColosseumPrivate ;


const unsigned int luminance_quantization_tables [][JPEGSAMPLESIZE] =
{
QTABLEDODQ1,
QTABLEDODQ2,
QTABLENIMAQ1,
QTABLEJPEGL1,
QTABLENIMAQ2,
QTABLEDODQ3,
QTABLENIMAQ3,
QTABLEDODQ4,
QTABLENIMAQ4,
QTABLEBEST
} ;

const unsigned int chrominance_quantization_tables [][JPEGSAMPLESIZE] =
{
QTABLEDODQ1,
QTABLEDODQ2,
QTABLENIMAQ1,
QTABLEJPEGC1,
QTABLENIMAQ2,
QTABLEDODQ3,
QTABLENIMAQ3,
QTABLEDODQ4,
QTABLENIMAQ4,
QTABLEBEST
} ;

namespace
{
const unsigned int DEFAULTQUALITY = 7 ;

const unsigned int MAXQUALITY = sizeof (luminance_quantization_tables)
                     / sizeof (luminance_quantization_tables [0])  - 1;


}
namespace Colosseum
{
//
//  Description:
//
//    Class Default Constructor
//
JpegEncoder::JpegEncoder ()
{
  initialize () ;
  return ;
}

//
//  Description:
//
//    Class Copy Constructor
//
JpegEncoder::JpegEncoder (const JpegEncoder &source)
{
  initialize () ;
  doCopy (source) ;
  return ;
}

//
//  Description:
//
//    Class Destructor
//
JpegEncoder::~JpegEncoder ()
{
  delete [] ac_tables ; ac_tables = 0 ;
  delete [] dc_tables ; dc_tables = 0 ;
  delete [] quantization_tables ; quantization_tables = 0 ;
  return ;
}
//
//  Description:
//
//    Class assignment operator.
//
JpegEncoder &JpegEncoder::operator=(const JpegEncoder &source)
{
  if (&source != this)
    doCopy (source) ;
  return *this ;
}
//
//  Description:
//
//    Class copy function. This function gets called from the
//    copy constructor and assignment operator.  The only thing
//    we copy are the use settable parameters.
//
void JpegEncoder::doCopy (const JpegEncoder &source)
{
  gray_scale = source.gray_scale ;
  rows_per_restart = source.rows_per_restart ;
  comment_string = source.comment_string ;
  image_quality = source.image_quality ;
  progressive_mode = source.progressive_mode ;

  for (unsigned int ii = 0 ; ii < MAXSCANS ; ++ ii)
    image_scans [ii] = source.image_scans [ii] ;

  BitmapImageCoder::operator=(source) ;
  memcpy (quantization_table_assignments,
          source.quantization_table_assignments,      
          sizeof (quantization_table_assignments)) ;
  memcpy (quantization_tables,
          source.quantization_tables,
          sizeof (quantization_tables)) ;
  return ;
}
//
//  Description:
//
//    This function writes an image to the output stream.
//
//  Parameters:
//    strm:   The output stream to write the image to. This most be opened in
//            binary mode
//    image:  The image to output.
//
void JpegEncoder::writeImage (JpegOutputStream &outputstream, const BitmapImage &image)
{

  image_components [YCOMPONENT].setOutputStream (outputstream) ;
  image_components [CBCOMPONENT].setOutputStream (outputstream) ;
  image_components [CRCOMPONENT].setOutputStream (outputstream) ;

  for (unsigned int ii = 0 ; ii < JPEGMAXQUANTIZATIONTABLES ; ++ ii)
    quantization_tables [ii].isUsed (false) ;

  if (gray_scale)
  {
    quantization_tables [quantization_table_assignments [YCOMPONENT]].isUsed (true) ;
  }
  else
  {
    quantization_tables [quantization_table_assignments [YCOMPONENT]].isUsed (true) ;
    quantization_tables [quantization_table_assignments [CBCOMPONENT]].isUsed (true) ;
    quantization_tables [quantization_table_assignments [CRCOMPONENT]].isUsed (true) ;
  }
BILLSELLSPOOP
  for (unsigned int ii = 0 ; ii < JPEGMAXQUANTIZATIONTABLES ; ++ ii)
  {
    if (quantization_tables [ii].isUsed ())
    {
      quantization_tables [ii].buildScaledTables () ;
    }
  }
ENDBILLSELLSPOOP

  frame_height = image.getHeight () ;
  frame_width = image.getWidth () ;

  // Find the MCU size and maximum sampling frequencies.
  calculateMcuDimensions () ;

  // Validate Parameters. If there is an error this function will throw
  // an exception.
  validateParameters () ;

  countPassesForProgressReporting () ;

  // Write the image header.
  outputMarker (outputstream, SOI) ;
  outputMarker (outputstream, APP0) ;
  outputJfifHeader (outputstream) ;

  if (comment_string != "")
    printComment (outputstream, comment_string) ;

  printComment (outputstream, "Created using the Colosseum Builders JPEG library") ;

  if (progressive_mode)
    printProgressiveFrame (outputstream, image) ;
  else
    printSequentialFrame (outputstream, image) ;

  outputMarker (outputstream, EOI) ;

  // Make sure that we are not holding on to any memory we do not
  // need any more.
  image_components [YCOMPONENT].freeDynamicStorage () ;
  image_components [CBCOMPONENT].freeDynamicStorage () ;
  image_components [CRCOMPONENT].freeDynamicStorage () ;

  return ;
}
//
//  Description:
//
//    Class Initialization function. This function is intended to be
//    called from constructors.
//
void JpegEncoder::initialize ()
{

  dc_tables = new JpegHuffmanEncoder [2] ;
  ac_tables = new JpegHuffmanEncoder [2] ;
  quantization_tables = new JpegEncoderQuantizationTable [JPEGMAXQUANTIZATIONTABLES] ;

  memset (image_scans, 0, sizeof (image_scans)) ;
  image_scans [0].component_mask = (1<<YCOMPONENT)
                                 |(1<<CBCOMPONENT)
                                 |(1<<CRCOMPONENT) ;
  progressive_mode = false ;
  setQuality (DEFAULTQUALITY) ;
  progress_function = 0 ;
  progress_data = 0 ;
  restart_interval = 0 ;
  rows_per_restart = 0 ;
  gray_scale = false ;

  for (unsigned int ii = 0 ; ii < MAXCOMPONENTS ; ++ ii)
  {
    image_components [ii].setHorizontalFrequency (1) ;
    image_components [ii].setVerticalFrequency (1) ;
  }
  quantization_table_assignments [YCOMPONENT] = 0 ;
  quantization_table_assignments [CBCOMPONENT] = 1 ;
  quantization_table_assignments [CRCOMPONENT] = 1 ;


  image_components [YCOMPONENT].setHuffmanTables (dc_tables [0],
                                                  ac_tables [0]) ;
  image_components [CBCOMPONENT].setHuffmanTables (dc_tables [1],
                                                   ac_tables [1]) ;
  image_components [CRCOMPONENT].setHuffmanTables (dc_tables [1],
                                                   ac_tables [1]) ;

  image_components [YCOMPONENT].setQuantizationTable (
      quantization_tables [quantization_table_assignments [YCOMPONENT]]) ; ;
  image_components [CBCOMPONENT].setQuantizationTable (
      quantization_tables [quantization_table_assignments [CBCOMPONENT]]) ; ;
  image_components [CRCOMPONENT].setQuantizationTable (
      quantization_tables [quantization_table_assignments [CRCOMPONENT]]) ; ;

  return ;
}

//
//  This function writes a marker to the output stream.
//
//  Parameters:
//    marker: The marker to be written to the output stream
//
void JpegEncoder::outputMarker (JpegOutputStream &outputstream, 
                                ColosseumPrivate::JpegMarker marker)
{
  outputstream.writeByte (SOB) ;
  outputstream.writeByte (marker) ;
  return ;
}

//
//  Description:
//
//    This function validates the user-set output parameters. If an error is
//    detected an EJpegBadOutputParameter exception is thrown.
//

void JpegEncoder::validateParameters ()
{
  const int ALL = (1<<YCOMPONENT)|(1<<CBCOMPONENT)|(1<<CRCOMPONENT) ;
  // Ensure we do not have fractional sampling of pixels.
  if (! gray_scale)
  {
    if (image_components [YCOMPONENT].getHorizontalFrequency () == 3
        || image_components [CBCOMPONENT].getHorizontalFrequency () == 3
        || image_components [CRCOMPONENT].getHorizontalFrequency () == 3)
    {
      if (image_components [YCOMPONENT].getHorizontalFrequency () == 2
          || image_components [YCOMPONENT].getHorizontalFrequency () == 4
          || image_components [CBCOMPONENT].getHorizontalFrequency () == 2
          || image_components [CBCOMPONENT].getHorizontalFrequency () == 4
          || image_components [CRCOMPONENT].getHorizontalFrequency () == 2
          || image_components [CRCOMPONENT].getHorizontalFrequency () == 4)
      {
        throw JpegError ("Fractional Horizontal Sampling") ;
      }
    }

    if (image_components [YCOMPONENT].getVerticalFrequency () == 3
        || image_components [CBCOMPONENT].getVerticalFrequency () == 3
        || image_components [CRCOMPONENT].getVerticalFrequency () == 3)
    {
      if (image_components [YCOMPONENT].getVerticalFrequency () == 2
          || image_components [YCOMPONENT].getVerticalFrequency () == 4
          || image_components [CBCOMPONENT].getVerticalFrequency () == 2
          || image_components [CBCOMPONENT].getVerticalFrequency () == 4
          || image_components [CRCOMPONENT].getVerticalFrequency () == 2
          || image_components [CRCOMPONENT].getVerticalFrequency () == 4)
      {
        throw JpegError ("Fractional Vertical Sampling") ;
      }
    }
  }

  if (progressive_mode)
  {
    // For a progressive scan the following rules apply
    //
    // o The spectral selection start can be zero if and only if
    // the spectral selection end is zero.
    //
    // o For each component the zero spectral selection start must occur
    // before any other.
    //
    // o If the spectral selection start is not zero there may only be
    // one component in a scan.
    //
    // o The entire spectral range must be specified for each component.
    //
    // o There can be no overlapping spectral ranges in scans.
    //
    int lasty = -1 ;
    int lastcb = -1 ;
    int lastcr = -1 ;

    if (gray_scale)
    {
      // For a grayscale image the Cb and Cr components are not used.
      lastcb = 63 ;
      lastcr = 63 ;
    }

    for (scan_count = 0 ; scan_count < MAXSCANS ; ++ scan_count)
    {
      if (lasty == 63 && lastcb == 63 && lastcr == 63)
        break ;

      if (image_scans [scan_count].component_mask == 0)
        throw JpegError ("Scan contains no components") ;

      if (image_scans [scan_count].successive_approximation
          > JPEGMAXSUCCESSIVAPPROXIMATION)
      {
        throw JpegError ("Successive Approximation too large") ;
      }
        
      image_scans [scan_count].successive_approximation_low =
        image_scans [scan_count].successive_approximation ;
      image_scans [scan_count].successive_approximation_high = 0 ;

      // Y Component Validation
      if ((image_scans [scan_count].component_mask & (1<<YCOMPONENT)) != 0)
      {
        if (image_scans [scan_count].spectral_selection_end == 0)
        {
          if (lasty != -1)
            throw JpegError ("Duplicate Y Component Scan") ;

          image_scans [scan_count].spectral_selection_start = 0 ;
          lasty = 0 ;
        }
        else
        {
          if (image_scans [scan_count].component_mask != (1<<YCOMPONENT))
            throw JpegError ("Multiple Components in AC Scan") ;

          if (image_scans [scan_count].spectral_selection_end != 0
              && lasty == -1)
          {
            throw JpegError (
                     "AC Scan specified before DC scan for Y Component") ;
          }

          if (image_scans [scan_count].spectral_selection_end <= lasty)
            throw JpegError ("Duplicate or overlapping spectral selection for Y Component") ;

          image_scans [scan_count].spectral_selection_start = lasty + 1 ;
          lasty = image_scans [scan_count].spectral_selection_end ;
        }
      }

      if (! gray_scale)
      {
        // Cb Component Validation
        if ((image_scans [scan_count].component_mask & (1<<CBCOMPONENT)) != 0)
        {
          if (image_scans [scan_count].spectral_selection_end == 0)
          {
            if (lastcb != -1)
              throw JpegError ("Duplicate Cb Component Scan") ;

            image_scans [scan_count].spectral_selection_start = 0 ;
            lastcb = 0 ;
          }
          else
          {
            if (image_scans [scan_count].component_mask != (1<<CBCOMPONENT))
              throw JpegError ("Multiple Components in AC Scan") ;

            if (image_scans [scan_count].spectral_selection_end != 0 && lastcb == -1)
              throw JpegError ("AC Scan specified before DC scan for cb Component") ;

            if (image_scans [scan_count].spectral_selection_end <= lastcb)
            {
              throw JpegError (
              "Duplicate or overlapping spectral selection for Cb Component") ;
            }

            image_scans [scan_count].spectral_selection_start = lastcb + 1 ;
            lastcb = image_scans [scan_count].spectral_selection_end ;
          }
        }

        // Cr Component Validation
        if ((image_scans [scan_count].component_mask & (1<<CRCOMPONENT)) != 0)
        {
          if (image_scans [scan_count].spectral_selection_end == 0)
          {
            if (lastcr != -1)
              throw JpegError ("Duplicate Cr Component Scan") ;

            image_scans [scan_count].spectral_selection_start = 0 ;
            lastcr = 0 ;
          }
          else
          {
            if (image_scans [scan_count].component_mask != (1<<CRCOMPONENT))
              throw JpegError ("Multiple Components in AC Scan") ;

            if (image_scans [scan_count].spectral_selection_end != 0
                && lastcr== -1)
            {
              throw JpegError (
                      "AC Scan specified before DC scan for Cr Component") ;
            }

            if (image_scans [scan_count].spectral_selection_end <= lastcr)
            {
              throw JpegError (
             "Duplicate or overlapping spectral selection for Cr Component") ;
            }
            image_scans [scan_count].spectral_selection_start = lastcr + 1 ;
            lastcr = image_scans [scan_count].spectral_selection_end ;
          }
        }
      }
    }
    if (lasty != 63)
      throw JpegError ("Y Component not completely defined by scans") ;
    if (! gray_scale)
    {
      if (lastcb != 63)
        throw JpegError ("Cb Component not completely defined by scans") ;
      if (lastcr != 63)
        throw JpegError ("Cr Component not completely defined by scans") ;
    }
  }
  else
  {
    if (! gray_scale)
    {
      if ((image_scans [0].component_mask
          |image_scans [1].component_mask
          |image_scans [2].component_mask)
          != ALL)
      {
        throw JpegError ("Not all components specified in scans") ;
      }
      if ((image_scans [0].component_mask
           +image_scans [1].component_mask
           +image_scans [2].component_mask)
          != ALL)
      {
        throw JpegError ("Component in more than one scan") ;
      }
      if (image_scans [2].component_mask != 0)
        scan_count = 3 ;
      else if (image_scans [1].component_mask != 0)
        scan_count = 2 ;
      else
        scan_count = 1 ;
    }
    else
    {
      scan_count = 1 ;
    }
  }

  // Enforce the MCU size limitation in Section B.2.3
  //
  // I am not certain why this limitation of 10 data units per MCU even
  // exists. It seems to be an silly arbitrary limit. Maybe its to set
  // a maximum upper size for an MCU decoding buffer. In any event we
  // must abide by it.
  if (! gray_scale)
  {
    for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
    {
      unsigned int dataunits = 0 ;
      unsigned int componentcount = 0 ;
      if ((image_scans [ii].component_mask & (1<<YCOMPONENT)) != 0)
      {
        dataunits += image_components [YCOMPONENT].getHorizontalFrequency ()
                     * image_components [YCOMPONENT].getVerticalFrequency () ;
        ++ componentcount ;
      }
      if ((image_scans [ii].component_mask & (1<<CBCOMPONENT)) != 0)
      {
        dataunits += image_components [CBCOMPONENT].getHorizontalFrequency ()
                  * image_components [CBCOMPONENT].getVerticalFrequency () ;
        ++ componentcount ;
      }
      if ((image_scans [ii].component_mask & (1<<CRCOMPONENT)) != 0)
      {
        dataunits += image_components [CRCOMPONENT].getHorizontalFrequency ()
                  * image_components [CRCOMPONENT].getVerticalFrequency () ;
        ++ componentcount ;
      }
      if (dataunits > JPEGMAXDATAUNITSPERMCU && componentcount > 1)
        throw JpegError ("Data units in MCU exceeds 10") ;
    }
  }
  return ;
}
//
//  Description:
//
//    This function writes a comment marker to the output stream.
//
//  Parameters:
//    str:  The comment string
//
void JpegEncoder::printComment (JpegOutputStream &outputstream, const std::string &str)
{
  unsigned int length = sizeof (UBYTE2) + str.length () + 1 ;
  outputMarker (outputstream, COM) ;
  outputstream.writeBigEndianWord (length) ;
  outputstream.write (str.c_str (), str.length ()) ;
  outputstream.writeByte (0) ; // Terminator
  return ;
}


//
//  Description:
//
//    This function writes the quantization tables to the output stream.
//
void JpegEncoder::printQuantizationTables (JpegOutputStream &outputstream)
{
  const int precision = 0 ; // Byte Precision
  UBYTE1 pqtq ;

  // Count the number of quantization tables needed by the frame and
  // create the scaled version of the table used in the DCT.
  unsigned int tablecount = 0 ;
  for (unsigned int ii = 0 ; ii < JPEGMAXQUANTIZATIONTABLES ; ++ ii)
  {
    if (quantization_tables [ii].isUsed ())
      ++ tablecount ;
  }

  // Section B.2.4.1
  outputMarker (outputstream, DQT) ;
  outputstream.writeBigEndianWord (sizeof(UBYTE2) + tablecount * (sizeof (pqtq) + JPEGSAMPLESIZE)) ;

BILLSELLSPOOP
  for (unsigned int ii = 0 ; ii < JPEGMAXQUANTIZATIONTABLES ; ++ ii)
  {
    if (quantization_tables [ii].isUsed ())
    {
      pqtq = (precision << 4) | ii ;
      outputstream.writeByte (pqtq) ;
      for (unsigned int jj = 0 ; jj < JPEGSAMPLESIZE ; ++ jj)
        outputstream.writeByte (quantization_tables [ii][JpegZigZagInputOrder (jj)]) ;
    }
  }
ENDBILLSELLSPOOP
  return ;
}

//
//  Description:
//
//    This function is called by a scan to write a restart interval to the
//    output stream. We do not output a Define Restart Interval marker if
//    the restart interval is not changing.
//
//  Parameters:
//    restartinterval: The new restart interval
//
void JpegEncoder::outputRestartInterval (JpegOutputStream &outputstream,
                                         unsigned int restartinterval)
{
  if (restartinterval == restart_interval)
    return ;
  restart_interval =  restartinterval ;

  // B.2.4.4
  outputMarker (outputstream, DRI) ;
  outputstream.writeBigEndianWord (4) ;
  outputstream.writeBigEndianWord (restartinterval) ;
  return ;
}


//
//  Description:
//
//    This function writes a sequential frame to the output stream. We create
//    frames with either one (black & white) or three (color) components.
//
//  Parameters:
//    image: The image to be written in the frame
//
void JpegEncoder::printSequentialFrame (JpegOutputStream &outputstream,
                                        const BitmapImage &image)
{
  // Sample the components
  if (gray_scale)
  {
    ++ current_pass ;
    JpegEncoderComponent::grayScaleConvert (*this,
                                            image, 
                                            image_components [YCOMPONENT]) ;
  }
  else
  {
    ++ current_pass ;
    JpegEncoderComponent::rgbConvert (*this,
                                      image, 
                                      max_horizontal_frequency,
                                      max_vertical_frequency,
                                      image_components [YCOMPONENT], 
                                      image_components [CBCOMPONENT], 
                                      image_components [CRCOMPONENT]) ;
    ++ current_pass ;
    image_components [YCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;
    ++ current_pass ;
    image_components [CBCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;
    ++ current_pass ;
    image_components [CRCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;
  }

  // Write the Frame Header
  // Section B.2.2

  outputMarker (outputstream, SOF0) ;
  if (gray_scale)  // Length
    outputstream.writeBigEndianWord (8 + 3) ;
  else
    outputstream.writeBigEndianWord (8 + 3 * 3) ;

  outputstream.writeByte (8) ; // Precision
  outputstream.writeBigEndianWord (image.getHeight ()) ;
  outputstream.writeBigEndianWord (image.getWidth ()) ;

  if (gray_scale)
  {
    outputstream.writeByte (1) ;  // Component Count
    outputstream.writeByte(YCOMPONENT) ;
    outputstream.writeByte (
        (image_components [YCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [YCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [YCOMPONENT]) ;


    printQuantizationTables (outputstream) ;
    scan_component_count = 1 ;
    scan_components [0] = &image_components [YCOMPONENT] ;
    ++ current_pass ;
    printSequentialScan (outputstream, image_scans [0]) ;
  }
  else
  {
    outputstream.writeByte (3) ;  // Component Count
    outputstream.writeByte(YCOMPONENT) ;
    outputstream.writeByte (
        (image_components [YCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [YCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [YCOMPONENT]) ;

    outputstream.writeByte(CBCOMPONENT) ;
    outputstream.writeByte (
        (image_components [CBCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [CBCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [CBCOMPONENT]) ;

    outputstream.writeByte(CRCOMPONENT) ;
    outputstream.writeByte (
        (image_components [CRCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [CRCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [CRCOMPONENT]) ;

    printQuantizationTables (outputstream) ;

    // Print the scans that make up the frame.
    for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
    {
      ++ current_pass ;
      if (image_scans [ii].component_mask != 0)
      {
        findComponentsInScan (image_scans [ii]) ;
        printSequentialScan (outputstream, image_scans [ii]) ;
      }
    }
  }

  return ;
}

//
//  Description:
//
//    This function writes a progressive frame to the output stream.
//
//  Parameters:
//    image: The image to be written in the frame
//
void JpegEncoder::printProgressiveFrame (JpegOutputStream &outputstream,
                                         const BitmapImage &image)
{
  if (gray_scale)
  {
    ++ current_pass ;
    JpegEncoderComponent::grayScaleConvert (*this,
                                            image, 
                                            image_components [YCOMPONENT]) ;
    // Output JPEG SOF Marker
    // Section B.2.2
    outputMarker (outputstream, SOF2) ;
    outputstream.writeBigEndianWord (8 + 3) ;  

    outputstream.writeByte (8) ; // Precision
    outputstream.writeBigEndianWord (image.getHeight ()) ;
    outputstream.writeBigEndianWord (image.getWidth ()) ;

    outputstream.writeByte (1) ;  // Component Count
    outputstream.writeByte(YCOMPONENT) ;
    outputstream.writeByte (
        (image_components [YCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [YCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [YCOMPONENT]) ;

    printQuantizationTables (outputstream) ;

    scan_component_count = 1 ;
    scan_components [0] = &image_components [YCOMPONENT] ;

    bool doingwork ;
    do
    {
      doingwork = false ;
      for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
      {
        if ((image_scans [ii].component_mask & (1<<YCOMPONENT)) != 0
             && image_scans [ii].successive_approximation_low >= 0)
        {
          ++ current_pass ;
          printProgressiveScan (outputstream, image_scans [ii]) ;
          doingwork = true ;
          image_scans [ii].successive_approximation_high
            = image_scans [ii].successive_approximation_low ;
          -- image_scans [ii].successive_approximation_low ;
        }
      }
    } while (doingwork) ;
  }
  else
  {
    ++ current_pass ;
    JpegEncoderComponent::rgbConvert (*this,
                                      image, 
                                      max_horizontal_frequency,
                                      max_vertical_frequency,
                                      image_components [YCOMPONENT], 
                                      image_components [CBCOMPONENT], 
                                      image_components [CRCOMPONENT]) ;
    ++ current_pass ;
    image_components [YCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;
    ++ current_pass ;
    image_components [CBCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;
    ++ current_pass ;
    image_components [CRCOMPONENT].sampleComponent (*this,
                                                    max_horizontal_frequency,
                                                    max_vertical_frequency) ;

    // Output JPEG SOS Marker
    // Section B.2.2
    outputMarker (outputstream, SOF2) ;
    outputstream.writeBigEndianWord (8 + 3 * 3) ;

    outputstream.writeByte (8) ; // Precision
    outputstream.writeBigEndianWord (image.getHeight ()) ;
    outputstream.writeBigEndianWord (image.getWidth ()) ;

    outputstream.writeByte (3) ;  // Component Count
    outputstream.writeByte(YCOMPONENT) ;
    outputstream.writeByte (
        (image_components [YCOMPONENT].getHorizontalFrequency () << 4)
        | image_components [YCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [YCOMPONENT]) ;

    outputstream.writeByte(CBCOMPONENT) ;
    outputstream.writeByte (
        (image_components [CBCOMPONENT].getHorizontalFrequency () << 4)
         | image_components [CBCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [CBCOMPONENT]) ;  // Quantization Table

    outputstream.writeByte(CRCOMPONENT) ;
    outputstream.writeByte (
        (image_components [CRCOMPONENT].getHorizontalFrequency () << 4)
         | image_components [CRCOMPONENT].getVerticalFrequency ()) ;
    outputstream.writeByte (quantization_table_assignments [CRCOMPONENT]) ;  // Quantization Table

    printQuantizationTables (outputstream) ;

    // Because of successive approximation we may have to go through the
    // scan list several times. This flag gets set each time a scan
    // is processed. The first time we search the scan list and do
    // nothing this flag will be false and we are all done.
    bool doingwork ;
    do
    {
      doingwork = false ;
      for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
      {
        findComponentsInScan (image_scans [ii]) ;

        if (scan_component_count != 0
            && image_scans [ii].successive_approximation_low >= 0)
        {
          ++ current_pass ;
          printProgressiveScan (outputstream, image_scans [ii]) ;
          doingwork = true ;
          image_scans [ii].successive_approximation_high
            = image_scans [ii].successive_approximation_low ;
          -- image_scans [ii].successive_approximation_low ;
        }
      }
    } while (doingwork) ;

  }
  return ;
}


//
//  Description:
//
//    This function enables or disables progressive output. When the mode is
//    changed the image_scans is initialized with a default set of values for
//    the new mode.
//
//  Parameters:
//    value: (true=>Progressive Mode, false=>Sequential Mode)
//
void JpegEncoder::setProgressive (bool value)
{
  if (value == progressive_mode)
    return ;

  progressive_mode = value ;
  memset (image_scans, 0, sizeof (image_scans)) ;
  if (progressive_mode)
  {
    // For Progressive Scans our default is four scans.  The first
    // contains the DC coefficients for all three components. The next
    // three scans contain the AC coefficients for each component.

    image_scans [0].component_mask = (1<<YCOMPONENT)
                                    |(1<<CBCOMPONENT)
                                    |(1<<CRCOMPONENT) ;
    image_scans [1].component_mask = (1<<YCOMPONENT) ;
    image_scans [1].spectral_selection_end = 63 ;
    image_scans [2].component_mask = (1<<CBCOMPONENT) ;
    image_scans [2].spectral_selection_end = 63 ;
    image_scans [3].component_mask = (1<<CRCOMPONENT) ;
    image_scans [3].spectral_selection_end = 63 ;
  }
  else
  {
    // For sequential mode the default is to create one scan with
    // all components.
    image_scans [0].component_mask = (1<<YCOMPONENT)
                                   |(1<<CBCOMPONENT)
                                   |(1<<CRCOMPONENT) ;
  }
  return ;
}

//
//  Description:
//
//    This function writes a sequential scan to the output stream.
//
//  Parameters:
//    scan:   The scan descriptor
//
void JpegEncoder::printSequentialScan (JpegOutputStream &outputstream,
                                       const Scan &scan)
{
  ASSERT (scan_component_count != 0) ; // No components defined for scan 

  // Output the restart interval and the Huffman tables. We must set the
  // restart interval *BEFORE* gathering statistics.
  if (scan_component_count != 1)
  {
    outputRestartInterval (outputstream, rows_per_restart * mcu_cols) ;
  }
  else
  {
    outputRestartInterval (outputstream,
                           rows_per_restart * scan_components [0]->dataUnitCols ()) ;
  }

  // Gather Huffman Statistics
  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    dc_tables [0].reset () ;
    ac_tables [0].reset () ;
  }
  if ((scan.component_mask & (1<<YCOMPONENT)) == 0
      || scan_component_count != 1)
  {
    ac_tables [1].reset () ;
    dc_tables [1].reset () ;
  }


  if (scan_component_count != 1)
  {
    interleavedPass (
          outputstream,
          false,
          &JpegEncoderComponent::encodeSequential,
          &JpegEncoderComponent::gatherDcData,
          &JpegEncoderComponent::gatherAcData,
          0, JPEGSAMPLESIZE - 1, 0) ;
  }
  else
  {
    noninterleavedPass (
          outputstream,
          false,
          &JpegEncoderComponent::encodeSequential,
          &JpegEncoderComponent::gatherDcData,
          &JpegEncoderComponent::gatherAcData,
          0, JPEGSAMPLESIZE - 1, 0) ;
  }

  // Create the Huffman Tables
  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    dc_tables [0].buildTable () ;
    ac_tables [0].buildTable () ;
  }
  if ((scan.component_mask & (1<<YCOMPONENT)) == 0
       || scan_component_count != 1)
  {
    ac_tables [1].buildTable () ;
    dc_tables [1].buildTable () ;
  }

  // Output the Huffman tables
  printHuffmanTables (outputstream, scan, true, true) ; // Output DC and AC tables.

  // Create the scan marker.
  // Section B.2.3
  outputMarker (outputstream, SOS) ;
  outputstream.writeBigEndianWord (6 + 2 * scan_component_count) ;  // Length
  outputstream.writeByte (scan_component_count) ;  // Component Count

  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    outputstream.writeByte (0x01) ; // YCOMPONENT
    outputstream.writeByte (0x00) ; // Entropy Tables
  }
  if (! gray_scale)
  {
    if ((scan.component_mask & (1<<CBCOMPONENT)) != 0)
    {
      outputstream.writeByte (0x02) ; // CBCOMPONENT
      outputstream.writeByte (0x11) ; // Entropy Tables
    }
    if ((scan.component_mask & (1<<CRCOMPONENT)) != 0)
    {
      outputstream.writeByte (0x03) ; // CRCOMPONENT
      outputstream.writeByte (0x11) ; // Entropy Tables
    }
  }

  outputstream.writeByte (0) ; // Spectral Selection Start
  outputstream.writeByte (JPEGSAMPLESIZE - 1) ; // Spectral Selection End
  outputstream.writeByte (0) ; // Successive Approximation

  // Output the Scan data.
  outputstream.enterBitMode (CHAR_BIT) ;
  if (scan_component_count != 1)
  {
    interleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::encodeSequential,
          &JpegEncoderComponent::printDcData,
          &JpegEncoderComponent::printAcData,
          0, JPEGSAMPLESIZE - 1, 0) ;
  }
  else
  {
    noninterleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::encodeSequential,
          &JpegEncoderComponent::printDcData,
          &JpegEncoderComponent::printAcData,
          0, JPEGSAMPLESIZE - 1, 0) ;
  }
  outputstream.exitBitMode () ;
  return ;
}

//
//  Description:
//
//    This function makes a pass through the data units for a non-interleaved
//    Scan.
//
//    The reason for having a generic function that uses pointers to member
//    functions to do processing is that there are several places where the
//    control processing has to be identical in multiple passes where the
//    low level actions are different. For example, the processing here when
//    gathering Huffman statistics has to be identical to the processing when
//    writing values to the output file. Using separate functions turned out
//    to be error prone due to the difficulty of keeping multiple functions
//    exactly in synchronization.
//
//  Parameters:
//    writerestarts: false => This is a statistics gathering pass
//                   true => This pass is writing data to the output file.
//    passfunctions: Pointer to EncoderComponent member function used
//                   to process this pass.
//    dcfunction:    Pointer to the EncoderComponent member function used
//                   to process DC coefficient values.
//    acfunction:    Pointer to the EncoderComponent member function used
//                   to process AC coefficient values.
//    sss:           Spectral Selection Start (0-63)
//    sse:           Spectral Selection End (0-63)
//    ssa:           Successive Approximation (0-13)
//
void JpegEncoder::noninterleavedPass (
                    JpegOutputStream &outputstream,
                    bool writedata,
                    JpegEncoderComponent::COMPONENTPASSFUNCTION passfunction,
                    JpegEncoderComponent::DCOUTPUTFUNCTION dcfunction,
                    JpegEncoderComponent::ACOUTPUTFUNCTION acfunction,
                    unsigned int sss,
                    unsigned int sse,
                    unsigned int ssa)
{
  // We use a scale integer to keep trake of progress to lessen the
  // change that the progress increment will be zero.
  const int progressscale = 8 ;
  unsigned int progress ;
  unsigned int progressincrement ;
  if (writedata)
    progress = 50 << progressscale ;
  else
    progress = 0 ;
  progressincrement = (100 << progressscale)
                    / (2 * scan_components [0]->dataUnitRows ()) ;

  resetDcValues () ;

  unsigned int intervalcount = 0 ;
  unsigned int restartmarker = 0 ;
  for (unsigned int row = 0 ;
       row < scan_components [0]->noninterleavedDuRows () ;
       ++ row)
  {
    for (unsigned int col = 0 ;
         col < scan_components [0]->noninterleavedDuColumns () ;
         ++ col, ++ intervalcount)
    {
      if (restart_interval != 0)
      {
        if (intervalcount == restart_interval && restart_interval != 0)
        {
          // If there are any outstanding EOB runs we flush them before the
          // restart marker. This is not explicitly stated in the JPEG
          // standard. The definition of "Restart Interval" in section 4.1
          // states that restart markers separate independent sequences,
          // something we would not have if we did not output the EOB run
          // here.
          scan_components [0]->printEobRun (acfunction) ;
          // E.1.4
          if (writedata)
            outputRestartMarker (outputstream, restartmarker) ;
          resetDcValues () ;
          ++ restartmarker ;
          restartmarker %= 8 ;
          intervalcount = 0 ;
        }
      }
      (scan_components [0]->*passfunction) (
                                row,
                                col,
                                dcfunction,
                                acfunction,
                                sss, sse,
                                ssa) ;

    }
    progress += progressincrement ;
    callProgressFunction (progress >> progressscale) ;
  }
  if (writedata)
    callProgressFunction (100) ;
  else
    callProgressFunction (50) ;
  return ;
}

//
//  Description:
//
//    This function makes a pass through the data units for an interleaved
//    Scan.
//
//    The reason for having a generic function that uses pointers to member
//    functions to do processing is that there are several places where the
//    control processing has to be identical in multiple passes where the
//    low level actions are different. For example, the processing here when
//    gathering Huffman statistics has to be identical to the processing when
//    writing values to the output file. Using separate functions turned out
//    to be error prone due to the difficulty of keeping multiple functions
//    exactly in synchronization.
//
//  Parameters:
//     writerestarts: false => This is a statistics gathering pass
//                    true => This pass is writing data to the output file.
//     passfunctions: Pointer to EncoderComponent member function used
//                    to process this pass.
//     dcfunction:    Pointer to the EncoderComponent member function used
//                    to process DC coefficient values.
//     acfunction:    Pointer to the EncoderComponent member function used
//                    to process AC coefficient values.
//     sss:           Spectral Selection Start (0-63)
//     sse:           Spectral Selection End (0-63)
//     ssa:           Successive Approximation (0-13)
//
void JpegEncoder::interleavedPass (
                    JpegOutputStream &outputstream,
                    bool writedata,
                    JpegEncoderComponent::COMPONENTPASSFUNCTION passfunction,
                    JpegEncoderComponent::DCOUTPUTFUNCTION dcfunction,
                    JpegEncoderComponent::ACOUTPUTFUNCTION acfunction,
                    unsigned int sss,
                    unsigned int sse,
                    unsigned int ssa)
{
  const int progressscale = 8 ;
  unsigned int progress ;
  unsigned int progressincrement ;
  if (writedata)
    progress = 50 << progressscale ;
  else
    progress = 0 ;
  progressincrement = (100 << progressscale) / (2 * mcu_rows) ;

  resetDcValues () ;

  unsigned int intervalcount = 0 ;
  unsigned int restartmarker = 0 ;
  for (unsigned int mcurow = 0 ;
       mcurow < mcu_rows ;
       ++ mcurow)
  {
    for (unsigned int mcucol = 0 ;
         mcucol < mcu_cols ;
          ++ mcucol, ++ intervalcount)
    {
      // Handle Restart Markers
      if (restart_interval != 0)
      {
        if (intervalcount == restart_interval && restart_interval != 0)
        {
          if (writedata)
            outputRestartMarker (outputstream, restartmarker) ;
          resetDcValues () ;
          ++ restartmarker ;
          restartmarker %= 8 ;
          intervalcount = 0 ;
        }
      }

      for (unsigned int cc = 0 ; cc < scan_component_count ; ++ cc)
      {
        for (unsigned int cy = 0 ;
             cy < scan_components [cc]->getVerticalFrequency () ;
             ++ cy)
        {
          unsigned int durow = scan_components [cc]->getVerticalFrequency ()
                             * mcurow + cy ;
          for (unsigned int cx = 0 ;
               cx < scan_components [cc]->getHorizontalFrequency () ;
               ++ cx)
          {
            unsigned int ducol
              = scan_components [cc]->getHorizontalFrequency ()
              * mcucol + cx ;
            (scan_components [cc]->*passfunction) (durow, ducol,
                                              dcfunction, acfunction,
                                              sss, sse, ssa) ;
          }
        }
      }
    }
    progress += progressincrement ;
    callProgressFunction (progress >> progressscale) ;
  }
  if (writedata)
    callProgressFunction (100) ;
  else
    callProgressFunction (50) ;
  return ;
}

//
//  Description:
//
//    This function writes the Huffman tables used by a scan to the output
//    stream.
//
//    We only use two DC and two AC tables to be compatible with baseline
//    JPEG.  In progressive JPEG there is really no reason for more than
//    two tables for RGB images. If we ever go to four colors then things
//    may need to change.
//
//    The Y component always uses table ID 0. The Cb and Cr components always
//    use table ID 1.
//
//  Parameters:
//    scan:     Here the scan structure is used to determine which components
//              are part of the scan (Y and/or Cb/Cr)
//    usedc:    Set to true if the scan includes DC components. (Sequential
//              Scan or Progressive Scan with the spectral selection 0)
//    useac:    Set to true if the scan includes AC components. (Sequential
//              Scan or Progressive Scan with the spectral selection start
//              not zero)
//
void JpegEncoder::printHuffmanTables (JpegOutputStream &outputstream,
                                      const Scan &scan,
                                      bool usedc,
                                      bool useac)
{
  // Section B.2.4.2

  if ((scan.component_mask & (1 << YCOMPONENT)) != 0)
  {
    // See if this is a color image.
    if (scan_component_count != 1)
    {
      // We have at least two components and the first is the Y component.
      // This means we need the both of the huffman tables.
      // B.2.4.2
      outputMarker (outputstream, DHT) ;
      unsigned int size = sizeof (UBYTE2) ;
      if (usedc)
      {
        size += dc_tables [0].outputSize ()
                + dc_tables [1].outputSize () ;
      }
      if (useac)
      {
        size += ac_tables [0].outputSize ()
                + ac_tables [1].outputSize () ;
      }
      outputstream.writeBigEndianWord (size) ;
      if (usedc)
      {
        outputstream.writeByte (0x00) ;
        dc_tables [0].printTable (outputstream) ;
      }
      if (useac)
      {
        outputstream.writeByte (0x10) ;
        ac_tables [0].printTable (outputstream) ;
      }
      if (usedc)
      {
        outputstream.writeByte (0x01) ;
        dc_tables [1].printTable (outputstream) ;
      }
      if (useac)
      {
        outputstream.writeByte (0x11) ;
        ac_tables [1].printTable (outputstream) ;
      }
    }
    else
    {
      // The only component is Y
      unsigned int size = sizeof (UBYTE2) ;
      if (usedc)
      {
        size += dc_tables [0].outputSize () ;
      }
      if (useac)
      {
        size += ac_tables [0].outputSize () ;
      }

      outputMarker (outputstream, DHT) ;
      outputstream.writeBigEndianWord (size) ;
      if (usedc)
      {
        outputstream.writeByte (0x00) ;
        dc_tables [0].printTable (outputstream) ;
      }
      if (useac)
      {
        outputstream.writeByte (0x10) ;
        ac_tables [0].printTable (outputstream) ;
      }
    }
  }
  else
  {
    // The Y component is not present. Output is the same for
    // Cb, Cr, or Cb & Cr.
    unsigned int size = sizeof (UBYTE2) ;
    if (usedc)
    {
      size += dc_tables [1].outputSize () ;
    }
    if (useac)
    {
      size += ac_tables [1].outputSize () ;
    }

    outputMarker (outputstream, DHT) ;
    outputstream.writeBigEndianWord (size) ;
    if (usedc)
    {
      outputstream.writeByte (0x01) ;
      dc_tables [1].printTable (outputstream) ;
    }
    if (useac)
    {
      outputstream.writeByte (0x11) ;
      ac_tables [1].printTable (outputstream) ;
    }
  }
  return ;
}

//
//  Description:
//
//   This function resets the DC difference values for all
//   all the components in the scan.  We do this at the start of
//   each scan and whenever we output a restart marker.
//
void JpegEncoder::resetDcValues ()
{
  for (unsigned int ii = 0 ; ii < scan_component_count ; ++ ii)
    scan_components [ii]->resetDcDifference () ;
  return ;
}

//
//  Description:
//
//    This function determines the dimensions of an MCU using the maximum
//    sampling frequencies of the components.
//
void JpegEncoder::calculateMcuDimensions ()
{
  max_horizontal_frequency = 1 ;
  max_vertical_frequency = 1 ;

  if (! gray_scale)
  {
    for (unsigned int ii = YCOMPONENT ; ii <= CRCOMPONENT ; ++ ii)
    {
      if (image_components [ii].getHorizontalFrequency ()
          > max_horizontal_frequency)
      {
        max_horizontal_frequency
          = image_components [ii].getHorizontalFrequency () ;
      }

      if (image_components [ii].getVerticalFrequency ()
          > max_vertical_frequency)
      {
        max_vertical_frequency
          = image_components [ii].getVerticalFrequency () ;
      }
    }
  }
  else
  {
    max_horizontal_frequency
      = image_components [YCOMPONENT].getHorizontalFrequency () ;
    max_vertical_frequency
      = image_components [YCOMPONENT].getVerticalFrequency () ;
  }

  unsigned int mcuheight = max_vertical_frequency * JPEGSAMPLEWIDTH ;
  unsigned int mcuwidth = max_horizontal_frequency * JPEGSAMPLEWIDTH ;
  mcu_rows = (frame_height + mcuheight - 1) / mcuheight ;
  mcu_cols = (frame_width + mcuwidth - 1) / mcuwidth ;
  return ;
}

//
//  Description:
//
//    This function writes a progressive scan to the output stream.
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
void JpegEncoder::printProgressiveScan (JpegOutputStream &outputstream,
                                        const Scan &scan)
{
  ASSERT (scan_component_count != 0) ; // No components in progressive scan

  if (scan.spectral_selection_start == 0)
  {
    if (scan.successive_approximation_high == 0)
    {
      printDcFirst (outputstream, scan) ;
    }
    else
    {
      printDcRefine (outputstream, scan) ;
    }
  }
  else
  {
    ASSERT (scan_component_count == 1) ; // AC Scan does not have 1 component

    if (scan.successive_approximation_high == 0)
    {
      printAcFirst (outputstream, scan) ;
    }
    else
    {
      printAcRefine (outputstream, scan) ;
    }
  }
  return ;
}

//
//  Description:
//
//    This function handles scans containing the first pass for DC
//    coefficients.  If successive approximation is not used then
//    this would be the only DC coefficient pass. DC progressive
//    scans may be interleaved, unlike AC scans.
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
void JpegEncoder::printDcFirst (JpegOutputStream &outputstream, const Scan &scan)
{
  // Reset the Huffman statistics counters.
  if ((scan.component_mask & (1 << YCOMPONENT)) != 0)
  {
    dc_tables [0].reset () ;
  }
  if ((scan.component_mask & (1 << YCOMPONENT)) == 0
       || scan_component_count > 1)
  {
    dc_tables [1].reset () ;
  }

  outputRestartInterval (outputstream, restart_interval) ;

  // Gather the Huffman statistics
  if (scan_component_count != 1)
  {
    interleavedPass (
          outputstream,
          false,
          &JpegEncoderComponent::progressiveDcFirst,
          &JpegEncoderComponent::gatherDcData, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }
  else
  {
    noninterleavedPass (
          outputstream,
          false,
          &JpegEncoderComponent::progressiveDcFirst,
          &JpegEncoderComponent::gatherDcData, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }

  // Create the Huffman tables from the statistics
  if ((scan.component_mask & (1 << YCOMPONENT)) != 0)
  {
    dc_tables [0].buildTable () ;
  }
  if ((scan.component_mask & (1 << YCOMPONENT)) == 0
      || scan_component_count > 1)
  {
    dc_tables [1].buildTable () ;
  }

  printHuffmanTables (outputstream, scan, true, false) ;

  // Output the scan header.
  // Section B.2.3
  outputMarker (outputstream, SOS) ;
  outputstream.writeBigEndianWord (6 + 2 * scan_component_count) ;  // Length
  outputstream.writeByte (scan_component_count) ;  // Component Count


  if ((scan.component_mask & (1 << YCOMPONENT)) != 0)
  {
    outputstream.writeByte (YCOMPONENT) ;
    outputstream.writeByte (0x00) ; // Entropy Tables
  }
  if (! gray_scale)
  {
    if ((scan.component_mask & (1 << CBCOMPONENT)) != 0)
    {
      outputstream.writeByte (CBCOMPONENT) ;
      outputstream.writeByte (0x11) ; // Entropy Tables
    }
    if ((scan.component_mask & (1 << CRCOMPONENT)) != 0)
    {
      outputstream.writeByte (CRCOMPONENT) ;
      outputstream.writeByte (0x11) ; // Entropy Tables
    }
  }

  outputstream.writeByte (0) ; // Spectral Selection Start
  outputstream.writeByte (0) ; // Spectral Selection End
  int value = (scan.successive_approximation_high << 4)
            | scan.successive_approximation_low ;
  outputstream.writeByte (value) ; // Successive Approximation

  // Output the scan data.
  outputstream.enterBitMode (CHAR_BIT) ;
  if (scan_component_count != 1)
  {
    interleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::progressiveDcFirst,
          &JpegEncoderComponent::printDcData, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }
  else
  {
    noninterleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::progressiveDcFirst,
          &JpegEncoderComponent::printDcData, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }
  outputstream.exitBitMode () ;
  return ;
}

//
//  Description:
//
//    This function outputs the data for a refining DC scan. This type of
//    scan is unique in that it does use Huffman encoding.
//
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
void JpegEncoder::printDcRefine (JpegOutputStream &outputstream, const Scan &scan)
{
  // Output the scan header.
  // Section B.2.3
  outputMarker (outputstream, SOS) ;
  outputstream.writeBigEndianWord (6 + 2 * scan_component_count) ;  // Length
  outputstream.writeByte (scan_component_count) ;


  if ((scan.component_mask & (1 << YCOMPONENT)) != 0)
  {
    outputstream.writeByte (YCOMPONENT) ;
    outputstream.writeByte (0) ; // No Huffman table is used.
  }
  if (! gray_scale)
  {
    if ((scan.component_mask & (1 << CBCOMPONENT)) != 0)
    {
      outputstream.writeByte (CBCOMPONENT) ;
      outputstream.writeByte (0) ; // No Huffman table is used.
    }
    if ((scan.component_mask & (1 << CRCOMPONENT)) != 0)
    {
      outputstream.writeByte (CRCOMPONENT) ;
      outputstream.writeByte (0) ; // No Huffman table is used.
    }
  }

  outputstream.writeByte (0) ; // Spectral Selection Start
  outputstream.writeByte (0) ; // Spectral Selection End

  int value = (scan.successive_approximation_high << 4)
            | scan.successive_approximation_low ;
  outputstream.writeByte (value) ;

  // Output the scan data.
  outputstream.enterBitMode (CHAR_BIT) ;
  if (scan_component_count != 1)
  {
    interleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::progressiveDcRefine,
          0, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }
  else
  {
    noninterleavedPass (
          outputstream,
          true,
          &JpegEncoderComponent::progressiveDcRefine,
          0, 0,
          0, 0,
          scan.successive_approximation_low) ;
  }
  outputstream.exitBitMode () ;
  return ;
}

//
//  Description:
//
//    This function outputs a scan that is the first pass for a set of AC
//    coefficients.
//
//    Even though AC progressive scans cannot be interleaved, we follow the
//    convention of using Huffman Table #0 for the Y component and #1 for
//    the Cb and Cr components.
//
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
void JpegEncoder::printAcFirst (JpegOutputStream &outputstream, const Scan &scan)
{
  // Reset the Huffman statistics counters.
  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    ac_tables [0].reset () ;
  }
  else
  {
    ac_tables [1].reset () ;
  }

  outputRestartInterval (outputstream, restart_interval) ;

  // Gather the Huffman statistics
  firstAcData (outputstream, scan, false, &JpegEncoderComponent::gatherAcData) ;

  // Generate the Huffman statistics
  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    ac_tables [0].buildTable () ;
  }
  else
  {
    ac_tables [1].buildTable () ;
  }

  printHuffmanTables (outputstream, scan, false, true) ;

  // Section B.2.3
  outputMarker (outputstream, SOS) ;
  outputstream.writeBigEndianWord (6 + 2 * scan_component_count) ;  // Length
  outputstream.writeByte (scan_component_count) ;  // Component Count

  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    outputstream.writeByte (YCOMPONENT) ;
    outputstream.writeByte (0x00) ; // Entropy Table
  }
  else if ((scan.component_mask & (1<<CBCOMPONENT)) != 0)
  {
    outputstream.writeByte (CBCOMPONENT) ;
    outputstream.writeByte (0x11) ; // Entropy Tables
  }
  else if ((scan.component_mask & (1<<CRCOMPONENT)) != 0)
  {
    outputstream.writeByte (CRCOMPONENT) ;
    outputstream.writeByte (0x11) ; // Entropy Tables
  }

  outputstream.writeByte (scan.spectral_selection_start) ; // Spectral Selection Start
  outputstream.writeByte (scan.spectral_selection_end) ; // Spectral Selection End
  int value = (scan.successive_approximation_high << 4)
            | scan.successive_approximation_low ;
  outputstream.writeByte (value) ; // Successive Approximation

  outputstream.enterBitMode (CHAR_BIT) ;
  firstAcData (outputstream, scan, true, &JpegEncoderComponent::printAcData) ;
  outputstream.exitBitMode () ;
  return ;
}

//
//  Descriptio:
//
//    This function outputs the data for a scan that refines AC coefficients
//    through successive approximation.
//
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
void JpegEncoder::printAcRefine (JpegOutputStream &outputstream, const Scan &scan)
{
  // Reset the Huffman statistics counters.
  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
    ac_tables [0].reset () ;
  else
    ac_tables [1].reset () ;

  outputRestartInterval (outputstream, restart_interval) ;
  // Gather the Huffman statistics.
  refineAcData (outputstream, scan, false, &JpegEncoderComponent::gatherAcData) ;

  // Create the Huffman Table.
  if ((scan.component_mask & (1 <<YCOMPONENT)) != 0)
  {
    ac_tables [0].buildTable () ;
  }
  else
  {
    ac_tables [1].buildTable () ;
  }

  printHuffmanTables (outputstream, scan, false, true) ;  // Only output the AC table.

  // Create the scan header.
  // Section B.2.3
  outputMarker (outputstream, SOS) ;
  outputstream.writeBigEndianWord (6 + 2 * scan_component_count) ;  // Length
  outputstream.writeByte (scan_component_count) ;  // Component Count

  if ((scan.component_mask & (1<<YCOMPONENT)) != 0)
  {
    outputstream.writeByte (YCOMPONENT) ;
    outputstream.writeByte (0x00) ; // Entropy Tables
  }
  if ((scan.component_mask & (1<<CBCOMPONENT)) != 0)
  {
    outputstream.writeByte (CBCOMPONENT) ;
    outputstream.writeByte (0x11) ; // Entropy Tables
  }
  if ((scan.component_mask & (1<<CRCOMPONENT)) != 0)
  {
    outputstream.writeByte (CRCOMPONENT) ;
    outputstream.writeByte (0x11) ; // Entropy Tables
  }

  outputstream.writeByte (scan.spectral_selection_start) ; // Spectral Selection Start
  outputstream.writeByte (scan.spectral_selection_end) ; // Spectral Selection End
  int value = (scan.successive_approximation_high << 4)
            | scan.successive_approximation_low ;
  outputstream.writeByte (value) ; // Successive Approximation

  // Output the scan data.
  outputstream.enterBitMode (CHAR_BIT) ;
  refineAcData (outputstream, scan, true, &JpegEncoderComponent::printAcData) ;
  outputstream.exitBitMode () ;

  return ;
}

//
//  Description:
//
//    This function loops through the data in an initial AC scan. For
//    all scans other than AC progressive ones we use the same function for
//    this purpose. Due to the excessive complexity of AC progressive scans
//    we use a specialized function.
//
//    This function gets called twice for each scan. The first pass is used
//    to gather Huffman statistics. The second pass is to output the scan.
//    Having a common function ensures that Huffman statistics are gathered
//    in the exact same manner as the scan data is output.
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//    outputrestarts: flag to indicate if restart markers are to be output
//    acfunction: The member function to process a data unit
//
void JpegEncoder::firstAcData (
                        JpegOutputStream &outputstream, 
                        const Scan &scan,
                        bool outputrestarts,
                        JpegEncoderComponent::ACOUTPUTFUNCTION acfunction)
{
  // We use a scale integer to keep trake of progress to lessen the
  // change that the progress increment will be zero.
  const int progressscale = 8 ;
  unsigned int progress ;
  unsigned int progressincrement ;
  if (outputrestarts)
    progress = 50 << progressscale ;
  else
    progress = 0 ;
  progressincrement = (100 << progressscale)
                    / (2 * scan_components [0]->dataUnitRows ()) ;

  scan_components [0]->resetEobRun () ;

  unsigned int intervalcount = 0 ;  // Count between restarts
  unsigned int restartmarker = 0 ;  // Value 0..7
  for (unsigned int row = 0 ;
       row < scan_components [0]->dataUnitRows () ;
       ++ row)
  {
    for (unsigned int col = 0 ;
         col < scan_components [0]->dataUnitCols () ;
         ++ col, ++ intervalcount)
    {
      // See if we are using restart markers.
      if (restart_interval != 0)
      {
        // Is a restart marker needed.
        if (intervalcount == restart_interval)
        {
          // If there are any outstanding EOB runs we flush them before the
          // restart marker. This is not explicitly stated in the JPEG
          // standard. The definition of "Restart Interval" in section 4.1
          // states that restart markers separate independent sequences,
          // something we would not have if we did not output the EOB run
          // here.
          scan_components [0]->printEobRun (acfunction) ;
          // Here we rely on the relationship RST0|n = RSTn [n = 0..7]
          // Section E.1.4
          if (outputrestarts)
            outputRestartMarker (outputstream, restartmarker) ;
          ++ restartmarker ;
          restartmarker %= 8 ;
          intervalcount = 0 ;
        }
      }

      // Process the data unit
      scan_components [0]->progressiveAcFirst (
                                row,
                                col,
                                acfunction,
                                scan.spectral_selection_start,
                                scan.spectral_selection_end,
                                scan.successive_approximation_low) ;
    }
    progress += progressincrement ;
    callProgressFunction (progress >> progressscale) ;
  }
  // If there is a final end of band run then write it out.
  scan_components [0]->printEobRun (acfunction) ;
  if (outputrestarts)
    callProgressFunction (100) ;
  else
    callProgressFunction (50) ;
  return ;
}

//
//  Description
//
//    This function loops through the DC using in a refining AC scan. For
//    all scans other than AC progressive ones we use the same function for
//    this purpose. Due to the excessive complexity of AC progressive scans
//    we use a specialized function.
//
//    This function gets called twice for each scan. The first pass is used
//    to gather Huffman statistics. The second pass is to output the scan.
//    Having a common function ensures that Huffman statistics are gathered
//    in the exact same manner as the scan data is output.
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//    outputrestarts: flag to indicate if restart markers are to be output
//    acfunction: The member function to process a data unit
//
void JpegEncoder::refineAcData (
                      JpegOutputStream &outputstream, 
                      const Scan &scan,
                      bool outputrestarts,
                      JpegEncoderComponent::ACOUTPUTFUNCTION acfunction)
{
  // We use a scale integer to keep trake of progress to lessen the
  // change that the progress increment will be zero.
  const int progressscale = 8 ;
  unsigned int progress ;
  unsigned int progressincrement ;
  if (outputrestarts)
    progress = 50 << progressscale ;
  else
    progress = 0 ;
  progressincrement = (100 << progressscale)
                    / (2 * scan_components [0]->dataUnitRows ()) ;

  scan_components [0]->resetEobRun () ;

  unsigned int intervalcount = 0 ;  // Count between restart markers
  unsigned int restartmarker = 0 ;  // 0..7 => RST0..RST7
  for (unsigned int row = 0 ;
       row < scan_components [0]->dataUnitRows () ;
       ++ row)
  {
    for (unsigned int col = 0 ;
         col < scan_components [0]->dataUnitCols () ;
         ++ col, ++ intervalcount)
    {
      // Are we using restart markers?
      if (restart_interval != 0)
      {
        // Do we need to output a restart marker?
        if (intervalcount == restart_interval)
        {
          // If there are any outstanding EOB runs we flush them before the
          // restart marker. This is not explicitly stated in the JPEG
          // standard. The definition of "Restart Interval" in section 4.1
          // states that restart markers separate independent sequences,
          // something we would not have if we did not output the EOB run
          // here.
          scan_components [0]->printRefineEobRun (
                                    acfunction,
                                    scan.spectral_selection_start,
                                    scan.spectral_selection_end,
                                    scan.successive_approximation_low) ;
          // Section E.1.4
          if (outputrestarts)
            outputRestartMarker (outputstream, restartmarker) ;
          ++ restartmarker ;
          restartmarker %= 8 ;
          intervalcount = 0 ;
        }
      }
      scan_components [0]->progressiveAcRefine (
                                row,
                                col,
                                acfunction,
                                scan.spectral_selection_start,
                                scan.spectral_selection_end,
                                scan.successive_approximation_low) ;

    }
    progress += progressincrement ;
    callProgressFunction (progress >> progressscale) ;
  }
  // If there is a final end of band run then write it out.
  scan_components [0]->printRefineEobRun (acfunction,
                                     scan.spectral_selection_start,
                                     scan.spectral_selection_end,
                                     scan.successive_approximation_low) ;

  if (outputrestarts)
    callProgressFunction (100) ;
  else
    callProgressFunction (50) ;
  return ;
}

//
//  Description:
//
//    This function determines which components is in a given scan.
//
//  Parameters:
//    scan:  The structure that contains the scan parameters.
//
//  Implicit Outputs:
//    scan_component_count: The number of components in the scan.
//    scan_components:      The list of components in the scan.
//

void JpegEncoder::findComponentsInScan (Scan &scan)
{
  scan_component_count = 0 ;
  if ((scan.component_mask & (1 <<YCOMPONENT)) != 0)
  {
    scan_components [scan_component_count] = &image_components [YCOMPONENT] ;
    ++ scan_component_count ;
  }
  if (! gray_scale)
  {
    if ((scan.component_mask & (1 <<CBCOMPONENT)) != 0)
    {
      scan_components [scan_component_count]
        = &image_components [CBCOMPONENT] ;
      ++ scan_component_count ;
    }
    if ((scan.component_mask & (1 <<CRCOMPONENT)) != 0)
    {
      scan_components [scan_component_count]
        = &image_components [CRCOMPONENT] ;
      ++ scan_component_count ;
    }
  }
  return ;
}

//
//  Description:
//
//    This function sets the sampling frequencies for a component
//
//  Parameters:
//    component:  The component ID
//    hf:         Horizontal Frequency
//    vf:         Vertical Frequency
//
void JpegEncoder::setSamplingFrequency (unsigned int component,   
                                        unsigned int hf,
                                        unsigned int vf)
{
  if (component >= MAXCOMPONENTS)
    throw JpegError ("Invalid Component ID") ;

  if (hf > JPEGMAXSAMPLINGFREQUENCY || hf < JPEGMINSAMPLINGFREQUENCY)
    throw JpegError ("Invalid Horizontal Sampling Frequency") ;

  if (vf > JPEGMAXSAMPLINGFREQUENCY || vf < JPEGMINSAMPLINGFREQUENCY)
    throw JpegError ("Invalid Vertical Sampling Frequency") ;

  image_components [component].setHorizontalFrequency (hf) ;
  image_components [component].setVerticalFrequency (vf) ;
  return ;
}

//
//  Description:
//
//    This function gets the sampling frequencies for a component
//
//  Parameters:
//    component:  The component ID
//    hf:         Horizontal Frequency
//    vf:         Vertical Frequency
//
void JpegEncoder::getSamplingFrequency (unsigned int component,
                                        unsigned int &hf,
                                        unsigned int &vf)
{
  if (component >= MAXCOMPONENTS)
    throw JpegError ("Invalid Component ID") ;

  hf = image_components [component].getHorizontalFrequency () ;
  vf = image_components [component].getVerticalFrequency () ;
  return ;
}

#pragma comment (exestr, "Copyright 1998 Colosseum Builders Inc.")

//
//  Description:
//
//    This function writes the JFIF header for the image.
//
void JpegEncoder::outputJfifHeader (JpegOutputStream &outputstream)
{
  using namespace Jfif ;
  
  // Create the JFIF header.
  struct JfifHeader jfif ;
  jfif.identifier [0] = 'J' ;
  jfif.identifier [1] = 'F' ;
  jfif.identifier [2] = 'I' ;
  jfif.identifier [3] = 'F' ;
  jfif.identifier [4] = '\000' ;
  jfif.version [0] = 1 ;
  jfif.version [1] = 1 ;
  *jfif.units = 0 ;
  *jfif.xdensity = SystemToBigEndian (static_cast<UBYTE2>(1)) ;
  *jfif.ydensity = SystemToBigEndian (static_cast<UBYTE2>(1)) ;
  *jfif.xthumbnail = 0 ;
  *jfif.ythumbnail = 0 ;


  UBYTE2 length = SystemToBigEndian (UBYTE2(jfif.size + sizeof (UBYTE2))) ;
  outputstream.write (reinterpret_cast<char*>(&length), sizeof (UBYTE2)) ;
  outputstream.write (reinterpret_cast<char*>(jfif.buffer), jfif.size) ;
  return ;
}

//
//  Description:
//
//    This function sets the attributes for a scan.
//
//  Parameters:
//    scan:  Scan number (0..MaxScan - 1)
//    components: Bit mask of components to include inthe scan
//    sse:  The spectral selection end for the scan.  The
//          spectral selection start is the sse+1 of the previous
//          scan
//    ssa:  Initial successive approximation value for the scan.
//
void JpegEncoder::setScanAttributes (unsigned int scan,
                                     unsigned long components,
                                     unsigned int sse,
                                     unsigned int ssa)
{
  if (scan >= MAXSCANS)
    throw JpegError ("Scan Index Out of Range") ;

  image_scans [scan].component_mask = components ;
  image_scans [scan].spectral_selection_end = sse ;
  image_scans [scan].successive_approximation = ssa ;
  return ;
}

//
//  Description:
//
//    This fnction returns the attributes that have been defined
//    for a scan (revsere of SetScanAttributes).
//
//  Parameters:
//    scan: (in) The scan to retrieve information about
//    components: (out) Bitmask of component IDs in the scan
//    sse: (out) Spectral selection end
//    ssa: (out) Successive Approximation
//
void JpegEncoder::getScanAttributes (unsigned int scan,
                                     unsigned long &components,
                                     unsigned int &sse,
                                     unsigned int &ssa)
{
  if (scan >= MAXSCANS)
    throw JpegError ("Scan Index Out of Rang") ;

  components = image_scans [scan].component_mask ;
  sse = image_scans [scan].spectral_selection_end ;
  ssa = image_scans [scan].successive_approximation ;
}

//
//  Description:
//
//    This function counts the number of passes through the data required
//    to write the image. This value is passed t the progress function.
//
void JpegEncoder::countPassesForProgressReporting ()
{
  current_pass = 0 ;
  if (progressive_mode)
  {
    if (gray_scale)
    {
      total_passes = 1 ;
      for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
      {
        if ((image_scans [ii].component_mask & (1<<YCOMPONENT)) != 0)
        {
          total_passes += image_scans [ii].successive_approximation + 1 ;
        }
      }
    }
    else
    {
      total_passes = 4 ; // 1 Color Conversion, 1 (x3) for each component
      for (unsigned int ii = 0 ; ii < scan_count ; ++ ii)
      {
        total_passes += image_scans [ii].successive_approximation + 1 ;
      }
    }
  }
  else
  {
    if (gray_scale)
      total_passes = 2 ;
    else
      total_passes = 4 + scan_count ; // 1 Color Conversion, 1 (x3) for each component
  }
  return ;
}

//
//  Description:
//
//    This function returns the current quality value for the
//    encoder.
//
//  Return Value:
//    The quality value.
//
unsigned int JpegEncoder::getQuality () const
{
  return image_quality ;
}
//
//  Description:
//
//    This function returns the maximum quality value supported.
//
unsigned int JpegEncoder::getMaxQuality () 
{
  return MAXQUALITY ;
}



//
//  Description:
//
//    This function sets the image quality value.
//
//  Parameters:
//    value: The quality value (1..MAXQUALITY)
//
void JpegEncoder::setQuality (unsigned int value)
{
  if (value > MAXQUALITY)
    throw JpegError ("Value Out of Range") ;

  for (unsigned int ii = 0 ; ii < JPEGSAMPLESIZE ; ++ ii)
  {
    quantization_tables [0][ii] = luminance_quantization_tables [value][ii] ;
    quantization_tables [2][ii] = luminance_quantization_tables [value][ii] ;
    quantization_tables [1][ii] = chrominance_quantization_tables [value][ii] ;
    quantization_tables [3][ii] = chrominance_quantization_tables [value][ii] ;
  }

  image_quality = value ;
  return ;
}

//
//  Description:
//
//    This function writes a restart marker to the output stream.
//
//  Parameters:
//
//    outputstream : The output stream to write to
//    marker : The restart marker to output - an integer in the range 0 .. 7.
//

void JpegEncoder::outputRestartMarker (JpegOutputStream &outputstream, int marker)
{
  ASSERT (marker >= 0 && marker <= 7)
  outputstream.exitBitMode () ;
  outputMarker (outputstream, JpegMarker (RST0 | marker)) ;
  outputstream.enterBitMode (CHAR_BIT) ;
  return ;
}


void JpegEncoder::writeImageFile (const std::string &filename, const BitmapImage &image) 
{
  JpegOutputFileStream outputstream ;
  outputstream.open (filename.c_str ()) ;
  if (! outputstream)
  {
    std::string message = "Cannot Open Input File " ;
    message += strerror (errno) ;
    throw JpegError (message) ;
  }
  writeImage (outputstream, image) ;
  return ;
}


//
//  The RowsPerRestart parameter specifies the number of MCU rows
//  output between restart markers. A value of zero means no restart
//  markers are used.
//
unsigned int JpegEncoder::getRowsPerRestart () const
{
  return rows_per_restart ;
}

void JpegEncoder::setRowsPerRestart (unsigned int rows)
{
  rows_per_restart = rows ;
  return ;
}

//
// The Grayscale property determines if the output is in grayscale (true)
// or color (false).
//
void JpegEncoder::setGrayscale (bool value)
{
  gray_scale = value ;
  return ;
}

bool JpegEncoder::getGrayscale () const
{
  return gray_scale ;
}

void  JpegEncoder::setVerbose (bool state)
{
  verbose_flag = state ;
}

bool JpegEncoder::getVerbose () const
{
  return verbose_flag ;
}


} // End Namespace Colosseum
