//C-  -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001  AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//C- GNU General Public License for more details.
//C- 
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech 
//C- Software authorized us to replace the original DjVu(r) Reference 
//C- Library notice by the following text (see doc/lizard2002.djvu):
//C-
//C-  ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, Version 2. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
//C- | General Public License.   This grant only confers the right to 
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
//C- | the extent such infringement is reasonably necessary to enable 
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
//C- | any greater extent that may be necessary to utilize further 
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------
// 
// $Id: IW44EncodeCodec.cpp,v 1.11 2004/08/06 15:11:29 leonb Exp $
// $Name: release_3_5_15 $

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif

// - Author: Leon Bottou, 08/1998
// From: Leon Bottou, 1/31/2002
// Lizardtech has split this file into a decoder and an encoder.
// Only superficial changes.  The meat is mine.

#define IW44IMAGE_IMPLIMENTATION /* */



#include "IW44Image.h"
#include "ZPCodec.h"
#include "GBitmap.h"
#include "GPixmap.h"
#include "IFFByteStream.h"
#include "GRect.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "MMX.h"
#undef IWTRANSFORM_TIMER
#ifdef IWTRANSFORM_TIMER
#include "GOS.h"
#endif

#include <assert.h>
#include <string.h>
#include <math.h>

#ifndef NEED_DECODER_ONLY


#ifdef HAVE_NAMESPACES
namespace DJVU {
# ifdef NOT_DEFINED // Just to fool emacs c++ mode
}
#endif
#endif

#define IWALLOCSIZE    4080
#define IWCODEC_MAJOR     1
#define IWCODEC_MINOR     2
#define DECIBEL_PRUNE   5.0


//////////////////////////////////////////////////////
// WAVELET DECOMPOSITION CONSTANTS
//////////////////////////////////////////////////////

// Parameters for IW44 wavelet.
// - iw_norm: norm of all wavelets (for db estimation)
// - iw_shift: scale applied before decomposition


static const float iw_norm[16] = {
  2.627989e+03F,
  1.832893e+02F, 1.832959e+02F, 5.114690e+01F,
  4.583344e+01F, 4.583462e+01F, 1.279225e+01F,
  1.149671e+01F, 1.149712e+01F, 3.218888e+00F,
  2.999281e+00F, 2.999476e+00F, 8.733161e-01F,
  1.074451e+00F, 1.074511e+00F, 4.289318e-01F
};

static const int iw_shift  = 6;
static const int iw_round  = (1<<(iw_shift-1));

static const struct { int start; int size; }  
bandbuckets[] = 
{
  // Code first bucket and number of buckets in each band
  { 0, 1 }, // -- band zero contains all lores info
  { 1, 1 }, { 2, 1 }, { 3, 1 }, 
  { 4, 4 }, { 8, 4 }, { 12,4 }, 
  { 16,16 }, { 32,16 }, { 48,16 }, 
};


/** IW44 encoded gray-level image.  This class provided functions for managing
    a gray level image represented as a collection of IW44 wavelet
    coefficients.  The coefficients are stored in a memory efficient data
    structure.  Member function \Ref{get_bitmap} renders an arbitrary segment
    of the image into a \Ref{GBitmap}.  Member functions \Ref{decode_iff} and
    \Ref{encode_iff} read and write DjVu IW44 files (see \Ref{IW44Image.h}).
    Both the copy constructor and the copy operator are declared as private
    members. It is therefore not possible to make multiple copies of instances
    of this class. */

class IWBitmap::Encode : public IWBitmap
{
public:
  /// Destructor
  virtual ~Encode(void);
  /** Null constructor.  Constructs an empty IWBitmap object. This object does
      not contain anything meaningful. You must call function \Ref{init},
      \Ref{decode_iff} or \Ref{decode_chunk} to populate the wavelet
      coefficient data structure. */
  Encode(void);
  /** Initializes an IWBitmap with image #bm#.  This constructor
      performs the wavelet decomposition of image #bm# and records the
      corresponding wavelet coefficient.  Argument #mask# is an optional
      bilevel image specifying the masked pixels (see \Ref{IW44Image.h}). */
  void init(const GBitmap &bm, const GP<GBitmap> mask=0);
  // CODER
  /** Encodes one data chunk into ByteStream #bs#.  Parameter #parms# controls
      how much data is generated.  The chunk data is written to ByteStream
      #bs# with no IFF header.  Successive calls to #encode_chunk# encode
      successive chunks.  You must call #close_codec# after encoding the last
      chunk of a file. */
  virtual int  encode_chunk(GP<ByteStream> gbs, const IWEncoderParms &parms);
  /** Writes a gray level image into DjVu IW44 file.  This function creates a
      composite chunk (identifier #FORM:BM44#) composed of #nchunks# chunks
      (identifier #BM44#).  Data for each chunk is generated with
      #encode_chunk# using the corresponding parameters in array #parms#. */
  virtual void encode_iff(IFFByteStream &iff, int nchunks, const IWEncoderParms *parms);
  /** Resets the encoder/decoder state.  The first call to #decode_chunk# or
      #encode_chunk# initializes the coder for encoding or decoding.  Function
      #close_codec# must be called after processing the last chunk in order to
      reset the coder and release the associated memory. */
  virtual void close_codec(void);
protected:
  Codec::Encode *ycodec_enc;
};

/** IW44 encoded color image. This class provided functions for managing a
    color image represented as a collection of IW44 wavelet coefficients.  The
    coefficients are stored in a memory efficient data structure.  Member
    function \Ref{get_pixmap} renders an arbitrary segment of the image into a
    \Ref{GPixmap}.  Member functions \Ref{decode_iff} and \Ref{encode_iff}
    read and write DjVu IW44 files (see \Ref{IW44Image.h}).  Both the copy
    constructor and the copy operator are declared as private members. It is
    therefore not possible to make multiple copies of instances of this
    class. */

class IWPixmap::Encode : public IWPixmap
{
public:
  enum CRCBMode { 
    CRCBnone=IW44Image::CRCBnone, 
    CRCBhalf=IW44Image::CRCBhalf, 
    CRCBnormal=IW44Image::CRCBnormal, 
    CRCBfull=IW44Image::CRCBfull };
  /// Destructor
  virtual ~Encode(void);
  /** Null constructor.  Constructs an empty IWPixmap object. This object does
      not contain anything meaningful. You must call function \Ref{init},
      \Ref{decode_iff} or \Ref{decode_chunk} to populate the wavelet
      coefficient data structure. */
  Encode(void);
  /** Initializes an IWPixmap with color image #bm#.  This constructor
      performs the wavelet decomposition of image #bm# and records the
      corresponding wavelet coefficient.  Argument #mask# is an optional
      bilevel image specifying the masked pixels (see \Ref{IW44Image.h}).
      Argument #crcbmode# specifies how the chrominance information should be
      encoded (see \Ref{CRCBMode}). */
  void init(const GPixmap &bm, const GP<GBitmap> mask=0, CRCBMode crcbmode=CRCBnormal);
  // CODER
  /** Encodes one data chunk into ByteStream #bs#.  Parameter #parms# controls
      how much data is generated.  The chunk data is written to ByteStream
      #bs# with no IFF header.  Successive calls to #encode_chunk# encode
      successive chunks.  You must call #close_codec# after encoding the last
      chunk of a file. */
  virtual int  encode_chunk(GP<ByteStream> gbs, const IWEncoderParms &parms);
  /** Writes a color image into a DjVu IW44 file.  This function creates a
      composite chunk (identifier #FORM:PM44#) composed of #nchunks# chunks
      (identifier #PM44#).  Data for each chunk is generated with
      #encode_chunk# using the corresponding parameters in array #parms#. */
  virtual void encode_iff(IFFByteStream &iff, int nchunks, const IWEncoderParms *parms);
  /** Resets the encoder/decoder state.  The first call to #decode_chunk# or
      #encode_chunk# initializes the coder for encoding or decoding.  Function
      #close_codec# must be called after processing the last chunk in order to
      reset the coder and release the associated memory. */
  virtual void close_codec(void);
protected:
  Codec::Encode *ycodec_enc, *cbcodec_enc, *crcodec_enc;
};

class IW44Image::Map::Encode : public IW44Image::Map // DJVU_CLASS
{
public:
  Encode(const int w, const int h) : Map(w,h) {}
  // creation (from image)
  void create(const signed char *img8, int imgrowsize, 
              const signed char *msk8=0, int mskrowsize=0);
  // slash resolution
  void slashres(int res);
};

class IW44Image::Codec::Encode : public IW44Image::Codec
{
public:
  Encode(IW44Image::Map &map);
  // Coding
  virtual int code_slice(ZPCodec &zp);
  float estimate_decibel(float frac);
  // Data
  void encode_buckets(ZPCodec &zp, int bit, int band,
    IW44Image::Block &blk, IW44Image::Block &eblk, int fbucket, int nbucket);
  int encode_prepare(int band, int fbucket, int nbucket, IW44Image::Block &blk, IW44Image::Block &eblk);
  IW44Image::Map emap;
};

IW44Image::Codec::Encode::Encode(IW44Image::Map &map)
: Codec(map), emap(map.iw,map.ih) {}

//////////////////////////////////////////////////////
/** IW44Image::Transform::Encode
*/

class IW44Image::Transform::Encode : IW44Image::Transform
{
 public:
 // WAVELET TRANSFORM
  /** Forward transform. */
  static void forward(short *p, int w, int h, int rowsize, int begin, int end);
  
  // COLOR TRANSFORM
  /** Extracts Y */
  static void RGB_to_Y(const GPixel *p, int w, int h, int rowsize, 
                       signed char *out, int outrowsize);
  /** Extracts Cb */
  static void RGB_to_Cb(const GPixel *p, int w, int h, int rowsize, 
                        signed char *out, int outrowsize);
  /** Extracts Cr */
  static void RGB_to_Cr(const GPixel *p, int w, int h, int rowsize, 
                        signed char *out, int outrowsize);
};


//////////////////////////////////////////////////////
// MMX IMPLEMENTATION HELPERS
//////////////////////////////////////////////////////


// Note:
// MMX implementation for vertical transforms only.
// Speedup is basically related to faster memory transfer
// The IW44 transform is not CPU bound, it is memory bound.

#ifdef MMX

static const short w9[]  = {9,9,9,9};
static const short w1[]  = {1,1,1,1};
static const int   d8[]  = {8,8};
static const int   d16[] = {16,16};


static inline void
mmx_fv_1 ( short* &q, short* e, int s, int s3 )
{
  while (q<e && (((long)q)&0x7))
    {
      int a = (int)q[-s] + (int)q[s];
      int b = (int)q[-s3] + (int)q[s3];
      *q -= (((a<<3)+a-b+8)>>4);
      q++;
    }
  while (q+3<e)
    {
      MMXar( movq,       q-s,mm0);  // MM0=[ b3, b2, b1, b0 ]
      MMXar( movq,       q+s,mm2);  // MM2=[ c3, c2, c1, c0 ]
      MMXrr( movq,       mm0,mm1);  
      MMXrr( punpcklwd,  mm2,mm0);  // MM0=[ c1, b1, c0, b0 ]
      MMXrr( punpckhwd,  mm2,mm1);  // MM1=[ c3, b3, c2, b2 ]
      MMXar( pmaddwd,    w9,mm0);   // MM0=[ (c1+b1)*9, (c0+b0)*9 ]
      MMXar( pmaddwd,    w9,mm1);   // MM1=[ (c3+b3)*9, (c2+b2)*9 ]
      MMXar( movq,       q-s3,mm2);
      MMXar( movq,       q+s3,mm4);
      MMXrr( movq,       mm2,mm3);
      MMXrr( punpcklwd,  mm4,mm2);  // MM2=[ d1, a1, d0, a0 ]
      MMXrr( punpckhwd,  mm4,mm3);  // MM3=[ d3, a3, d2, a2 ]
      MMXar( pmaddwd,    w1,mm2);   // MM2=[ (a1+d1)*1, (a0+d0)*1 ]
      MMXar( pmaddwd,    w1,mm3);   // MM3=[ (a3+d3)*1, (a2+d2)*1 ]
      MMXar( paddd,      d8,mm0);
      MMXar( paddd,      d8,mm1);
      MMXrr( psubd,      mm2,mm0);  // MM0=[ (c1+b1)*9-a1-d1+8, ...
      MMXrr( psubd,      mm3,mm1);  // MM1=[ (c3+b3)*9-a3-d3+8, ...
      MMXir( psrad,      4,mm0);
      MMXar( movq,       q,mm7);    // MM7=[ p3,p2,p1,p0 ]
      MMXir( psrad,      4,mm1);
      MMXrr( packssdw,   mm1,mm0);  // MM0=[ x3,x2,x1,x0 ]
      MMXrr( psubw,      mm0,mm7);  // MM7=[ p3-x3, p2-x2, ... ]
      MMXra( movq,       mm7,q);
#if defined(_MSC_VER) && defined(_DEBUG)
      MMXemms;
#endif
      q += 4;
    }
}

static inline void
mmx_fv_2 ( short* &q, short* e, int s, int s3 )
{
  while (q<e && (((long)q)&0x7))
    {
      int a = (int)q[-s] + (int)q[s];
      int b = (int)q[-s3] + (int)q[s3];
      *q += (((a<<3)+a-b+16)>>5);
      q ++;
    }
  while (q+3<e)
    {
      MMXar( movq,       q-s,mm0);  // MM0=[ b3, b2, b1, b0 ]
      MMXar( movq,       q+s,mm2);  // MM2=[ c3, c2, c1, c0 ]
      MMXrr( movq,       mm0,mm1);  
      MMXrr( punpcklwd,  mm2,mm0);  // MM0=[ c1, b1, c0, b0 ]
      MMXrr( punpckhwd,  mm2,mm1);  // MM1=[ c3, b3, c2, b2 ]
      MMXar( pmaddwd,    w9,mm0);   // MM0=[ (c1+b1)*9, (c0+b0)*9 ]
      MMXar( pmaddwd,    w9,mm1);   // MM1=[ (c3+b3)*9, (c2+b2)*9 ]
      MMXar( movq,       q-s3,mm2);
      MMXar( movq,       q+s3,mm4);
      MMXrr( movq,       mm2,mm3);
      MMXrr( punpcklwd,  mm4,mm2);  // MM2=[ d1, a1, d0, a0 ]
      MMXrr( punpckhwd,  mm4,mm3);  // MM3=[ d3, a3, d2, a2 ]
      MMXar( pmaddwd,    w1,mm2);   // MM2=[ (a1+d1)*1, (a0+d0)*1 ]
      MMXar( pmaddwd,    w1,mm3);   // MM3=[ (a3+d3)*1, (a2+d2)*1 ]
      MMXar( paddd,      d16,mm0);
      MMXar( paddd,      d16,mm1);
      MMXrr( psubd,      mm2,mm0);  // MM0=[ (c1+b1)*9-a1-d1+8, ...
      MMXrr( psubd,      mm3,mm1);  // MM1=[ (c3+b3)*9-a3-d3+8, ...
      MMXir( psrad,      5,mm0);
      MMXar( movq,       q,mm7);    // MM7=[ p3,p2,p1,p0 ]
      MMXir( psrad,      5,mm1);
      MMXrr( packssdw,   mm1,mm0);  // MM0=[ x3,x2,x1,x0 ]
      MMXrr( paddw,      mm0,mm7);  // MM7=[ p3+x3, p2+x2, ... ]
      MMXra( movq,       mm7,q);
#if defined(_MSC_VER) && defined(_DEBUG)
      MMXemms;
#endif
      q += 4;
    }
}

#endif /* MMX */

//////////////////////////////////////////////////////
// NEW FILTERS
//////////////////////////////////////////////////////

static void 
filter_fv(short *p, int w, int h, int rowsize, int scale)
{
  int y = 0;
  int s = scale*rowsize;
  int s3 = s+s+s;
  h = ((h-1)/scale)+1;
  y += 1;
  p += s;
  while (y-3 < h)
    {
      // 1-Delta
      {
        short *q = p;
        short *e = q+w;
        if (y>=3 && y+3<h)
          {
            // Generic case
#ifdef MMX
            if (scale==1 && MMXControl::mmxflag>0)
              mmx_fv_1(q, e, s, s3);
#endif
            while (q<e)
              {
                int a = (int)q[-s] + (int)q[s];
                int b = (int)q[-s3] + (int)q[s3];
                *q -= (((a<<3)+a-b+8)>>4);
                q += scale;
              }
          }
        else if (y<h)
          {
            // Special cases
            short *q1 = (y+1<h ? q+s : q-s);
            while (q<e)
              {
                int a = (int)q[-s] + (int)(*q1);
                *q -= ((a+1)>>1);
                q += scale;
                q1 += scale;
              }
          }
      }
      // 2-Update
      {
        short *q = p-s3;
        short *e = q+w;
        if (y>=6 && y<h)
          {
            // Generic case
#ifdef MMX
            if (scale==1 && MMXControl::mmxflag>0)
              mmx_fv_2(q, e, s, s3);
#endif
            while (q<e)
              {
                int a = (int)q[-s] + (int)q[s];
                int b = (int)q[-s3] + (int)q[s3];
                *q += (((a<<3)+a-b+16)>>5);
                q += scale;
              }
          }
        else if (y>=3)
          {
            // Special cases
            short *q1 = (y-2<h ? q+s : 0);
            short *q3 = (y<h ? q+s3 : 0);
            if (y>=6)
              {
                while (q<e)
                  {
                    int a = (int)q[-s] + (q1 ? (int)(*q1) : 0);
                    int b = (int)q[-s3] + (q3 ? (int)(*q3) : 0);
                    *q += (((a<<3)+a-b+16)>>5);
                    q += scale;
                    if (q1) q1 += scale;
                    if (q3) q3 += scale;
                  }
              }
            else if (y>=4)
              {
                while (q<e)
                  {
                    int a = (int)q[-s] + (q1 ? (int)(*q1) : 0);
                    int b = (q3 ? (int)(*q3) : 0);
                    *q += (((a<<3)+a-b+16)>>5);
                    q += scale;
                    if (q1) q1 += scale;
                    if (q3) q3 += scale;
                  }
              }
            else
              {
                while (q<e)
                  {
                    int a = (q1 ? (int)(*q1) : 0);
                    int b = (q3 ? (int)(*q3) : 0);
                    *q += (((a<<3)+a-b+16)>>5);
                    q += scale;
                    if (q1) q1 += scale;
                    if (q3) q3 += scale;
                  }
              }
          }
      }
      y += 2;
      p += s+s;
    }
}

static void 
filter_fh(short *p, int w, int h, int rowsize, int scale)
{
  int y = 0;
  int s = scale;
  int s3 = s+s+s;
  rowsize *= scale;
  while (y<h)
    {
      short *q = p+s;
      short *e = p+w;
      int a0=0, a1=0, a2=0, a3=0;
      int b0=0, b1=0, b2=0, b3=0;
      if (q < e)
        {
          // Special case: x=1
          a1 = a2 = a3 = q[-s];
          if (q+s<e)
            a2 = q[s];
          if (q+s3<e)
            a3 = q[s3];
          b3 = q[0] - ((a1+a2+1)>>1);
          q[0] = b3;
          q += s+s;
        }
      while (q+s3 < e)
        {
          // Generic case
          a0=a1; 
          a1=a2; 
          a2=a3;
          a3=q[s3];
          b0=b1; 
          b1=b2; 
          b2=b3;
          b3 = q[0] - ((((a1+a2)<<3)+(a1+a2)-a0-a3+8) >> 4);
          q[0] = b3;
          q[-s3] = q[-s3] + ((((b1+b2)<<3)+(b1+b2)-b0-b3+16) >> 5);
          q += s+s;
        }
      while (q < e)
        {
          // Special case: w-3 <= x < w
          a1=a2; 
          a2=a3;
          b0=b1; 
          b1=b2; 
          b2=b3;
          b3 = q[0] - ((a1+a2+1)>>1);
          q[0] = b3;
          q[-s3] = q[-s3] + ((((b1+b2)<<3)+(b1+b2)-b0-b3+16) >> 5);
          q += s+s;
        }
      while (q-s3 < e)
        {
          // Special case  w <= x < w+3
          b0=b1; 
          b1=b2; 
          b2=b3;
          b3=0;
          if (q-s3 >= p)
            q[-s3] = q[-s3] + ((((b1+b2)<<3)+(b1+b2)-b0-b3+16) >> 5);
          q += s+s;
        }
      y += scale;
      p += rowsize;
    }
}


//////////////////////////////////////////////////////
// WAVELET TRANSFORM 
//////////////////////////////////////////////////////


//----------------------------------------------------
// Function for applying bidimensional IW44 between 
// scale intervals begin(inclusive) and end(exclusive)

void
IW44Image::Transform::Encode::forward(short *p, int w, int h, int rowsize, int begin, int end)
{ 

  // PREPARATION
  filter_begin(w,h);
  // LOOP ON SCALES
  for (int scale=begin; scale<end; scale<<=1)
    {
#ifdef IWTRANSFORM_TIMER
      int tv,th;
      th = tv = GOS::ticks();
#endif
      filter_fh(p, w, h, rowsize, scale);
#ifdef IWTRANSFORM_TIMER
      th = GOS::ticks();
      tv = th - tv;
#endif
      filter_fv(p, w, h, rowsize, scale);
#ifdef IWTRANSFORM_TIMER
      th = GOS::ticks()-th;
      DjVuPrintErrorUTF8("forw%d\tv=%dms h=%dms\n", scale,th,tv);
#endif
    }
  // TERMINATE
  filter_end();
}

//////////////////////////////////////////////////////
// COLOR TRANSFORM 
//////////////////////////////////////////////////////

static const float 
rgb_to_ycc[3][3] = 
{ { 0.304348F,  0.608696F,  0.086956F },      
  { 0.463768F, -0.405797F, -0.057971F },
  {-0.173913F, -0.347826F,  0.521739F } };


/* Extracts Y */
void 
IW44Image::Transform::Encode::RGB_to_Y(const GPixel *p, int w, int h, int rowsize, 
                      signed char *out, int outrowsize)
{
  int rmul[256], gmul[256], bmul[256];
  for (int k=0; k<256; k++)
    {
      rmul[k] = (int)(k*0x10000*rgb_to_ycc[0][0]);
      gmul[k] = (int)(k*0x10000*rgb_to_ycc[0][1]);
      bmul[k] = (int)(k*0x10000*rgb_to_ycc[0][2]);
    }
  for (int i=0; i<h; i++, p+=rowsize, out+=outrowsize)
    {
      const GPixel *p2 = p;
      signed char *out2 = out;
      for (int j=0; j<w; j++,p2++,out2++)
        {
          int y = rmul[p2->r] + gmul[p2->g] + bmul[p2->b] + 32768;
          *out2 = (y>>16) - 128;
        }
    }
}

#ifdef min
#undef min
#endif
static inline int min(const int x,const int y) {return (x<y)?x:y;}
#ifdef max
#undef max
#endif
static inline int max(const int x,const int y) {return (x>y)?x:y;}

/* Extracts Cb */
void 
IW44Image::Transform::Encode::RGB_to_Cb(const GPixel *p, int w, int h, int rowsize, 
                       signed char *out, int outrowsize)
{
  int rmul[256], gmul[256], bmul[256];
  for (int k=0; k<256; k++)
    {
      rmul[k] = (int)(k*0x10000*rgb_to_ycc[2][0]);
      gmul[k] = (int)(k*0x10000*rgb_to_ycc[2][1]);
      bmul[k] = (int)(k*0x10000*rgb_to_ycc[2][2]);
    }
  for (int i=0; i<h; i++, p+=rowsize, out+=outrowsize)
    {
      const GPixel *p2 = p;
      signed char *out2 = out;
      for (int j=0; j<w; j++,p2++,out2++)
        {
          int c = rmul[p2->r] + gmul[p2->g] + bmul[p2->b] + 32768;
          *out2 = max(-128, min(127, c>>16));
        }
    }
}

/* Extracts Cr */
void 
IW44Image::Transform::Encode::RGB_to_Cr(const GPixel *p, int w, int h, int rowsize, 
                       signed char *out, int outrowsize)
{
  int rmul[256], gmul[256], bmul[256];
  for (int k=0; k<256; k++)
    {
      rmul[k] = (int)((k*0x10000)*rgb_to_ycc[1][0]);
      gmul[k] = (int)((k*0x10000)*rgb_to_ycc[1][1]);
      bmul[k] = (int)((k*0x10000)*rgb_to_ycc[1][2]);
    }
  for (int i=0; i<h; i++, p+=rowsize, out+=outrowsize)
    {
      const GPixel *p2 = p;
      signed char *out2 = out;
      for (int j=0; j<w; j++,p2++,out2++)
        {
          int c = rmul[p2->r] + gmul[p2->g] + bmul[p2->b] + 32768;
          *out2 = max(-128, min(127, c>>16));
        }
    }
}


//////////////////////////////////////////////////////
// MASKING DECOMPOSITION
//////////////////////////////////////////////////////

//----------------------------------------------------
// Function for applying bidimensional IW44 between 
// scale intervals begin(inclusive) and end(exclusive)
// with a MASK bitmap


static void
interpolate_mask(short *data16, int w, int h, int rowsize,
                 const signed char *mask8, int mskrowsize)
{
  int i,j;
  // count masked bits
  short *count;
  GPBuffer<short> gcount(count,w*h);
  short *cp = count;
  for (i=0; i<h; i++, cp+=w, mask8+=mskrowsize)
    for (j=0; j<w; j++)
      cp[j] = (mask8[j] ? 0 : 0x1000);
  // copy image
  short *sdata;
  GPBuffer<short> gsdata(sdata,w*h);
  short *p = sdata;
  short *q = data16;
  for (i=0; i<h; i++, p+=w, q+=rowsize)
    for (j=0; j<w; j++)
      p[j] = q[j];
  // iterate over resolutions
  int split = 1;
  int scale = 2;
  int again = 1;
  while (again && scale<w && scale<h)
    {
      again = 0;
      p = data16;
      q = sdata;
      cp = count;
      // iterate over block
      for (i=0; i<h; i+=scale, cp+=w*scale, q+=w*scale, p+=rowsize*scale)
        for (j=0; j<w; j+=scale)
          {
            int ii, jj;
            int gotz = 0;
            int gray = 0;
            int npix = 0;
            short *cpp = cp;
            short *qq = q;
            // look around when square goes beyond border
            int istart = i;
            if (istart+split>h)
              {
                istart -= scale;
                cpp -= w*scale;
                qq -= w*scale;
              }
            int jstart = j;
            if (jstart+split>w)
              jstart -= scale;
            // compute gray level
            for (ii=istart; ii<i+scale && ii<h; ii+=split, cpp+=w*split, qq+=w*split)
              for (jj=jstart; jj<j+scale && jj<w; jj+=split)
                {
                  if (cpp[jj]>0) 
                    {
                      npix += cpp[jj];
                      gray += cpp[jj] * qq[jj];
                    } 
                  else if (ii>=i && jj>=j)
                    {
                      gotz = 1;
                    }
                }
            // process result
            if (npix == 0)
              {
                // continue to next resolution
                again = 1;
                cp[j] = 0;
              }
            else
              {
                gray = gray / npix;
                // check whether initial image require fix
                if (gotz)
                  {
                    cpp = cp;
                    qq = p;
                    for (ii=i; ii<i+scale && ii<h; ii+=1, cpp+=w, qq+=rowsize)
                      for (jj=j; jj<j+scale && jj<w; jj+=1)
                        if (cpp[jj] == 0)
                          {
                            qq[jj] = gray;
                            cpp[jj] = 1;
                          }
                  }
                // store average for next iteration
                cp[j] = npix>>2;
                q[j] = gray;
              }
          }
      // double resolution
      split = scale;
      scale = scale+scale;
    }
}


static void
forward_mask(short *data16, int w, int h, int rowsize, int begin, int end,
             const signed char *mask8, int mskrowsize )
{
  int i,j;
  signed char *m;
  short *p;
  short *d;
  // Allocate buffers
  short *sdata;
  GPBuffer<short> gsdata(sdata,w*h);
  signed char *smask;
  GPBuffer<signed char> gsmask(smask,w*h);
  // Copy mask
  m = smask;
  for (i=0; i<h; i+=1, m+=w, mask8+=mskrowsize)
    memcpy((void*)m, (void*)mask8, w);
  // Loop over scale
  for (int scale=begin; scale<end; scale<<=1)
    {
      // Copy data into sdata buffer
      p = data16;
      d = sdata;
      for (i=0; i<h; i+=scale)
        {
          for (j=0; j<w; j+=scale)
            d[j] = p[j];
          p += rowsize * scale;
          d += w * scale;
        }
      // Decompose
      IW44Image::Transform::Encode::forward(sdata, w, h, w, scale, scale+scale);
      // Cancel masked coefficients
      d = sdata;
      m = smask;
      for (i=0; i<h; i+=scale+scale)
        {
          for (j=scale; j<w; j+=scale+scale)
            if (m[j])
              d[j] = 0;
          d += w * scale;
          m += w * scale;
          if (i+scale < h)
            {
              for (j=0; j<w; j+=scale)
                if (m[j])
                  d[j] = 0;
              d += w * scale;
              m += w * scale;
            }
        }
      // Reconstruct
      IW44Image::Transform::Decode::backward(sdata, w, h, w, scale+scale, scale);
      // Correct visible pixels
      p = data16;
      d = sdata;
      m = smask;
      for (i=0; i<h; i+=scale)
        {
          for (j=0; j<w; j+=scale)
            if (! m[j])
              d[j] = p[j];
          p += rowsize*scale;
          m += w*scale;
          d += w*scale;
        }
      // Decompose again (no need to iterate actually!)
      IW44Image::Transform::Encode::forward(sdata, w, h, w, scale, scale+scale);
      // Copy coefficients from sdata buffer
      p = data16;
      d = sdata;
      for (i=0; i<h; i+=scale)
        {
          for (j=0; j<w; j+=scale)
            p[j] = d[j];
          p += rowsize * scale;
          d += w * scale;
        }
      // Compute new mask for next scale
      m = smask;
      signed char *m0 = m;
      signed char *m1 = m;
      for (i=0; i<h; i+=scale+scale)
        {
          m0 = m1;
          if (i+scale < h)
            m1 = m + w*scale;
          for (j=0; j<w; j+=scale+scale)
            if (m[j] && m0[j] && m1[j] && (j<=0 || m[j-scale]) && (j+scale>=w || m[j+scale]))
              m[j] = 1;
            else
              m[j] = 0;
          m = m1 + w*scale;
        }
    }
  // Free buffers
}

void 
IW44Image::Map::Encode::create(const signed char *img8, int imgrowsize, 
               const signed char *msk8, int mskrowsize )
{
  int i, j;
  // Progress
  DJVU_PROGRESS_TASK(transf,"create iw44 map",3);
  // Allocate decomposition buffer
  short *data16;
  GPBuffer<short> gdata16(data16,bw*bh);
  // Copy pixels
  short *p = data16;
  const signed char *row = img8;
  for (i=0; i<ih; i++)
    {
      for (j=0; j<iw; j++)
        *p++ = (int)(row[j]) << iw_shift;
      row += imgrowsize;
      for (j=iw; j<bw; j++)
        *p++ = 0;
    }
  for (i=ih; i<bh; i++)
    for (j=0; j<bw; j++)
      *p++ = 0;
  // Handle bitmask
  if (msk8)
    {
      // Interpolate pixels below mask
      DJVU_PROGRESS_RUN(transf, 1);
      interpolate_mask(data16, iw, ih, bw, msk8, mskrowsize);
      // Multiscale iterative masked decomposition
      DJVU_PROGRESS_RUN(transf, 3);
      forward_mask(data16, iw, ih, bw, 1, 32, msk8, mskrowsize);
    }
  else
    {
      // Perform traditional decomposition
      DJVU_PROGRESS_RUN(transf, 3);
      IW44Image::Transform::Encode::forward(data16, iw, ih, bw, 1, 32);
    }
  // Copy coefficient into blocks
  p = data16;
  IW44Image::Block *block = blocks;
  for (i=0; i<bh; i+=32)
    {
      for (j=0; j<bw; j+=32)
        {
          short liftblock[1024];
          // transfer coefficients at (p+j) into aligned block
          short *pp = p + j;
          short *pl = liftblock;
          for (int ii=0; ii<32; ii++, pp+=bw)
            for (int jj=0; jj<32; jj++) 
              *pl++ = pp[jj];
          // transfer into IW44Image::Block (apply zigzag and scaling)
          block->read_liftblock(liftblock, this);
          block++;
        }
      // next row of blocks
      p += 32*bw;
    }
}

void 
IW44Image::Map::Encode::slashres(int res)
{
  int minbucket = 1;
  if (res < 2)
    return;
  else if (res < 4)
    minbucket=16;
  else if (res < 8)
    minbucket=4;
  for (int blockno=0; blockno<nb; blockno++)
    for (int buckno=minbucket; buckno<64; buckno++)
      blocks[blockno].zero(buckno);
}

// encode_prepare
// -- compute the states prior to encoding the buckets
int
IW44Image::Codec::Encode::encode_prepare(int band, int fbucket, int nbucket, IW44Image::Block &blk, IW44Image::Block &eblk)
{
  int bbstate = 0;
  // compute state of all coefficients in all buckets
  if (band) 
    {
      // Band other than zero
      int thres = quant_hi[band];
      char *cstate = coeffstate;
      for (int buckno=0; buckno<nbucket; buckno++, cstate+=16)
        {
          const short *pcoeff = blk.data(fbucket+buckno);
          const short *epcoeff = eblk.data(fbucket+buckno);
          int bstatetmp = 0;
          if (! pcoeff)
            {
              bstatetmp = UNK;
              // cstate[i] is not used and does not need initialization
            }
          else if (! epcoeff)
            {
              for (int i=0; i<16; i++)
                {
                  int cstatetmp = UNK;
                  if  ((int)(pcoeff[i])>=thres || (int)(pcoeff[i])<=-thres)
                    cstatetmp = NEW|UNK;
                  cstate[i] = cstatetmp;
                  bstatetmp |= cstatetmp;
                }
            }
          else
            {
              for (int i=0; i<16; i++)
                {
                  int cstatetmp = UNK;
                  if (epcoeff[i])
                    cstatetmp = ACTIVE;
                  else if  ((int)(pcoeff[i])>=thres || (int)(pcoeff[i])<=-thres)
                    cstatetmp = NEW|UNK;
                  cstate[i] = cstatetmp;
                  bstatetmp |= cstatetmp;
                }
            }
          bucketstate[buckno] = bstatetmp;
          bbstate |= bstatetmp;
        }
    }
  else
    {
      // Band zero ( fbucket==0 implies band==zero and nbucket==1 )
      const short *pcoeff = blk.data(0, &map);
      const short *epcoeff = eblk.data(0, &emap);
      char *cstate = coeffstate;
      for (int i=0; i<16; i++)
        {
          int thres = quant_lo[i];
          int cstatetmp = cstate[i];
          if (cstatetmp != ZERO)
            {
              cstatetmp = UNK;
              if (epcoeff[i])
                cstatetmp = ACTIVE;
              else if ((int)(pcoeff[i])>=thres || (int)(pcoeff[i])<=-thres)
                cstatetmp = NEW|UNK;
            }
          cstate[i] = cstatetmp;
          bbstate |= cstatetmp;
        }
      bucketstate[0] = bbstate;
    }
  return bbstate;
}

// encode_buckets
// -- code a sequence of buckets in a given block
void
IW44Image::Codec::Encode::encode_buckets(ZPCodec &zp, int bit, int band, 
                         IW44Image::Block &blk, IW44Image::Block &eblk,
                         int fbucket, int nbucket)
{
  // compute state of all coefficients in all buckets
  int bbstate = encode_prepare(band, fbucket, nbucket, blk, eblk);

  // code root bit
  if ((nbucket<16) || (bbstate&ACTIVE))
    {
      bbstate |= NEW;
    }
  else if (bbstate & UNK)
    {
      zp.encoder( (bbstate&NEW) ? 1 : 0 , ctxRoot);
#ifdef TRACE
      DjVuPrintMessage("bbstate[bit=%d,band=%d] = %d\n", bit, band, bbstate);
#endif
    }
  
  // code bucket bits
  if (bbstate & NEW)
    for (int buckno=0; buckno<nbucket; buckno++)
      {
        // Code bucket bit
        if (bucketstate[buckno] & UNK)
          {
            // Context
            int ctx = 0;
#ifndef NOCTX_BUCKET_UPPER
            if (band>0)
              {
                int k = (fbucket+buckno)<<2;
                const short *b = eblk.data(k>>4);
                if (b)
                  {
                    k = k & 0xf;
                    if (b[k])
                      ctx += 1;
                    if (b[k+1])
                      ctx += 1;
                    if (b[k+2])
                      ctx += 1;
                    if (ctx<3 && b[k+3])
                      ctx += 1;
                  }
              }
#endif
#ifndef NOCTX_BUCKET_ACTIVE
            if (bbstate & ACTIVE)
              ctx |= 4; 
#endif
            // Code
            zp.encoder( (bucketstate[buckno]&NEW) ? 1 : 0, ctxBucket[band][ctx] );
#ifdef TRACE
            DjVuPrintMessage("  bucketstate[bit=%d,band=%d,buck=%d] = %d\n", 
                   bit, band, buckno, bucketstate[buckno] & ~ZERO);
#endif
          }
      }
  
  // code new active coefficient (with their sign)
  if (bbstate & NEW)
    {
      int thres = quant_hi[band];
      char *cstate = coeffstate;
      for (int buckno=0; buckno<nbucket; buckno++, cstate+=16)
        if (bucketstate[buckno] & NEW)
          {
            int i;
#ifndef NOCTX_EXPECT
            int gotcha = 0;
            const int maxgotcha = 7;
            for (i=0; i<16; i++)
              if (cstate[i] & UNK)
                gotcha += 1;
#endif
            const short *pcoeff = blk.data(fbucket+buckno);
            short *epcoeff = eblk.data(fbucket+buckno, &emap);
            // iterate within bucket
            for (i=0; i<16; i++)
              {
                if (cstate[i] & UNK)
                  {
                    // Prepare context
                    int ctx = 0;
#ifndef NOCTX_EXPECT
                    if (gotcha>=maxgotcha)
                      ctx = maxgotcha;
                    else
                      ctx = gotcha;
#endif
#ifndef NOCTX_ACTIVE
                    if (bucketstate[buckno] & ACTIVE)
                      ctx |= 8;
#endif
                    // Code
                    zp.encoder( (cstate[i]&NEW) ? 1 : 0, ctxStart[ctx] );
                    if (cstate[i] & NEW)
                      {
                        // Code sign
                        zp.IWencoder( (pcoeff[i]<0) ? 1 : 0 );
                        // Set encoder state
                        if (band==0)
                          thres = quant_lo[i];
                        epcoeff[i] = thres + (thres>>1);
                      }
#ifndef NOCTX_EXPECT
                    if (cstate[i] & NEW)
                      gotcha = 0;
                    else if (gotcha > 0)
                      gotcha -= 1;
#endif
#ifdef TRACE
                    DjVuPrintMessage("    coeffstate[bit=%d,band=%d,buck=%d,c=%d] = %d\n", 
                           bit, band, buckno, i, cstate[i]);
#endif
                  }
              }
          }
    }

  // code mantissa bits
  if (bbstate & ACTIVE)
    {
      int thres = quant_hi[band];
      char *cstate = coeffstate;
      for (int buckno=0; buckno<nbucket; buckno++, cstate+=16)
        if (bucketstate[buckno] & ACTIVE)
          {
            const short *pcoeff = blk.data(fbucket+buckno);
            short *epcoeff = eblk.data(fbucket+buckno, &emap);
            for (int i=0; i<16; i++)
              if (cstate[i] & ACTIVE)
                {
                  // get coefficient
                  int coeff = pcoeff[i];
                  int ecoeff = epcoeff[i];
                  if (coeff < 0)
                    coeff = -coeff;
                  // get band zero thresholds
                  if (band == 0)
                    thres = quant_lo[i];
                  // compute mantissa bit
                  int pix = 0;
                  if (coeff >= ecoeff)
                    pix = 1;
                  // encode second or lesser mantissa bit
                  if (ecoeff <= 3*thres)
                    zp.encoder(pix, ctxMant);                      
                  else
					  zp.IWencoder(!!pix);
                  // adjust epcoeff
                  epcoeff[i] = ecoeff - (pix ? 0 : thres) + (thres>>1);
                }
          }
    }
}

// IW44Image::Codec::estimate_decibel
// -- estimate encoding error (after code_slice) in decibels.
float
IW44Image::Codec::Encode::estimate_decibel(float frac)
{
  int i,j;
  const float *q;
  // Fill norm arrays
  float norm_lo[16];
  float norm_hi[10];
  // -- lo coefficients
  q = iw_norm;
  for (i=j=0; i<4; j++)
    norm_lo[i++] = *q++;
  for (j=0; j<4; j++)
    norm_lo[i++] = *q;
  q += 1;
  for (j=0; j<4; j++)
    norm_lo[i++] = *q;
  q += 1;
  for (j=0; j<4; j++)
    norm_lo[i++] = *q;
  q += 1;
  // -- hi coefficients
  norm_hi[0] = 0;
  for (j=1; j<10; j++)
    norm_hi[j] = *q++;
  // Initialize mse array
  float *xmse;
  GPBuffer<float> gxmse(xmse,map.nb);
  // Compute mse in each block
  for (int blockno=0; blockno<map.nb; blockno++)
    {
      float mse = 0;
      // Iterate over bands
      for (int bandno=0; bandno<10; bandno++)
        {
          int fbucket = bandbuckets[bandno].start;
          int nbucket = bandbuckets[bandno].size;
          IW44Image::Block &blk = map.blocks[blockno];
          IW44Image::Block &eblk = emap.blocks[blockno];
          float norm = norm_hi[bandno];
          for (int buckno=0; buckno<nbucket; buckno++)
            {
              const short *pcoeff = blk.data(fbucket+buckno);
              const short *epcoeff = eblk.data(fbucket+buckno);
              if (pcoeff)
                {
                  if (epcoeff)
                    {
                      for (i=0; i<16; i++)
                        {
                          if (bandno == 0)
                            norm = norm_lo[i];
                          float delta = (float)(pcoeff[i]<0 ? -pcoeff[i] : pcoeff[i]);
                          delta = delta - epcoeff[i];
                          mse = mse + norm * delta * delta;
                        }
                    }
                  else
                    {
                      for (i=0; i<16; i++)
                        {
                          if (bandno == 0)
                            norm = norm_lo[i];
                          float delta = (float)(pcoeff[i]);
                          mse = mse + norm * delta * delta;
                        }
                    }
                }
            }
        }
      xmse[blockno] = mse / 1024;
    }
  // Compute partition point
  int n = 0;
  int m = map.nb - 1;
  int p = (int)floor(m*(1.0-frac)+0.5);
  p = (p>m ? m : (p<0 ? 0 : p));
  float pivot = 0;
  // Partition array
  while (n < p)
    {
      int l = n;
      int h = m;
      if (xmse[l] > xmse[h]) { float tmp=xmse[l]; xmse[l]=xmse[h]; xmse[h]=tmp; }
      pivot = xmse[(l+h)/2];
      if (pivot < xmse[l]) { float tmp=pivot; pivot=xmse[l]; xmse[l]=tmp; }
      if (pivot > xmse[h]) { float tmp=pivot; pivot=xmse[h]; xmse[h]=tmp; }
      while (l < h)
        {
          if (xmse[l] > xmse[h]) { float tmp=xmse[l]; xmse[l]=xmse[h]; xmse[h]=tmp; }
          while (xmse[l]<pivot || (xmse[l]==pivot && l<h)) l++;
          while (xmse[h]>pivot) h--;
        }
      if (p>=l) 
        n = l;
      else 
        m = l-1;
    }
  // Compute average mse
  float mse = 0;
  for (i=p; i<map.nb; i++)
    mse = mse + xmse[i];
  mse = mse / (map.nb - p);
  // Return
  float factor = 255 << iw_shift;
  float decibel = (float)(10.0 * log ( factor * factor / mse ) / 2.302585125);
  return decibel;
}




//////////////////////////////////////////////////////
// IW44IMAGE ENCODING ROUTINES
//////////////////////////////////////////////////////


void 
IW44Image::PrimaryHeader::encode(GP<ByteStream> gbs)
{
  gbs->write8(serial);
  gbs->write8(slices);
}

void 
IW44Image::SecondaryHeader::encode(GP<ByteStream> gbs)
{
  gbs->write8(major);
  gbs->write8(minor);
}

void 
IW44Image::TertiaryHeader::encode(GP<ByteStream> gbs)
{
  gbs->write8(xhi);
  gbs->write8(xlo);
  gbs->write8(yhi);
  gbs->write8(ylo);
  gbs->write8(crcbdelay);
}



GP<IW44Image>
IW44Image::create_encode(const ImageType itype)
{
  switch(itype)
  {
  case COLOR:
    return new IWPixmap::Encode();
  case GRAY:
    return new IWBitmap::Encode();
  default:
    return 0;
  }
}

GP<IW44Image>
IW44Image::create_encode(const GBitmap &bm, const GP<GBitmap> mask)
{
  IWBitmap::Encode *bit=new IWBitmap::Encode();
  GP<IW44Image> retval=bit;
  bit->init(bm, mask);
  return retval;
}


IWBitmap::Encode::Encode(void)
: IWBitmap(), ycodec_enc(0)
{}

IWBitmap::Encode::~Encode()
{
  close_codec();
}

void
IWBitmap::Encode::init(const GBitmap &bm, const GP<GBitmap> gmask)
{
  // Free
  close_codec();
  delete ymap;
  ymap = 0;
  // Init
  int i, j;
  int w = bm.columns();
  int h = bm.rows();
  int g = bm.get_grays()-1;
  signed char *buffer;
  GPBuffer<signed char> gbuffer(buffer,w*h);
  // Prepare gray level conversion table
  signed char  bconv[256];
  for (i=0; i<256; i++)
    bconv[i] = max(0,min(255,i*255/g)) - 128;
  // Perform decomposition
  // Prepare mask information
  const signed char *msk8 = 0;
  int mskrowsize = 0;
  GBitmap *mask=gmask;
  if (gmask)
  {
    msk8 = (const signed char*)((*mask)[0]);
    mskrowsize = mask->rowsize();
  }
  // Prepare a buffer of signed bytes
  for (i=0; i<h; i++)
    {
      signed char *bufrow = buffer + i*w;
      const unsigned char *bmrow = bm[i];
      for (j=0; j<w; j++)
        bufrow[j] = bconv[bmrow[j]];
    }
  // Create map
  Map::Encode *eymap=new Map::Encode(w,h);
  ymap = eymap;
  eymap->create(buffer, w, msk8, mskrowsize);
}

void 
IWBitmap::Encode::close_codec(void)
{
  delete ycodec_enc;
  ycodec_enc = 0;
  IWBitmap::close_codec();
}

int  
IWBitmap::Encode::encode_chunk(GP<ByteStream> gbs, const IWEncoderParms &parm)
{
  // Check
  if (parm.slices==0 && parm.bytes==0 && parm.decibels==0)
    G_THROW( ERR_MSG("IW44Image.need_stop") );
  if (! ymap)
    G_THROW( ERR_MSG("IW44Image.empty_object") );
  // Open codec
  if (!ycodec_enc)
  {
    cslice = cserial = cbytes = 0;
    ycodec_enc = new Codec::Encode(*ymap);
  }
  // Adjust cbytes
  cbytes += sizeof(struct IW44Image::PrimaryHeader);
  if (cserial == 0)
    cbytes += sizeof(struct IW44Image::SecondaryHeader) + sizeof(struct IW44Image::TertiaryHeader);
  // Prepare zcoded slices
  int flag = 1;
  int nslices = 0;
  GP<ByteStream> gmbs=ByteStream::create();
  ByteStream &mbs=*gmbs;
  DJVU_PROGRESS_TASK(chunk,"encode chunk",parm.slices-cslice);
  {
    float estdb = -1.0;
    GP<ZPCodec> gzp=ZPCodec::create(gmbs, true, true);
    ZPCodec &zp=*gzp;
    while (flag)
      {
        if (parm.decibels>0  && estdb>=parm.decibels)
          break;
        if (parm.bytes>0  && mbs.tell()+cbytes>=parm.bytes)
          break;
        if (parm.slices>0 && nslices+cslice>=parm.slices)
          break;
        DJVU_PROGRESS_RUN(chunk, (1+nslices-cslice)|0xf);
        flag = ycodec_enc->code_slice(zp);
        if (flag && parm.decibels>0.0)
          if (ycodec_enc->curband==0 || estdb>=parm.decibels-DECIBEL_PRUNE)
            estdb = ycodec_enc->estimate_decibel(db_frac);
        nslices++;
      }
  }
  // Write primary header
  struct IW44Image::PrimaryHeader primary;
  primary.serial = cserial;
  primary.slices = nslices;
  primary.encode(gbs);
  // Write auxilliary headers
  if (cserial == 0)
    {
      struct IW44Image::SecondaryHeader secondary;
      secondary.major = IWCODEC_MAJOR + 0x80;
      secondary.minor = IWCODEC_MINOR;
      secondary.encode(gbs);
      struct IW44Image::TertiaryHeader tertiary;
      tertiary.xhi = (ymap->iw >> 8) & 0xff;
      tertiary.xlo = (ymap->iw >> 0) & 0xff;
      tertiary.yhi = (ymap->ih >> 8) & 0xff;
      tertiary.ylo = (ymap->ih >> 0) & 0xff;
      tertiary.crcbdelay = 0;
      tertiary.encode(gbs);
    }
  // Write slices
  mbs.seek(0);
  gbs->copy(mbs);
  // Return
  cbytes  += mbs.tell();
  cslice  += nslices;
  cserial += 1;
  return flag;
}

void 
IWBitmap::Encode::encode_iff(IFFByteStream &iff, int nchunks, const IWEncoderParms *parms)
{
  if (ycodec_enc)
    G_THROW( ERR_MSG("IW44Image.left_open1") );
  int flag = 1;
  iff.put_chunk("FORM:BM44", 1);
  DJVU_PROGRESS_TASK(iff,"encode iff chunk",nchunks);
  for (int i=0; flag && i<nchunks; i++)
    {
      DJVU_PROGRESS_RUN(iff,i+1);
      iff.put_chunk("BM44");
      flag = encode_chunk(iff.get_bytestream(),parms[i]);
      iff.close_chunk();
    }
  iff.close_chunk();
  close_codec();
}

GP<IW44Image>
IW44Image::create_encode(
  const GPixmap &pm, const GP<GBitmap> gmask, CRCBMode crcbmode)
{
  IWPixmap::Encode *pix=new IWPixmap::Encode();
  GP<IW44Image> retval=pix;
  pix->init(pm, gmask,(IWPixmap::Encode::CRCBMode)crcbmode);
  return retval;
}

IWPixmap::Encode::Encode(void)
: IWPixmap(), ycodec_enc(0), cbcodec_enc(0), crcodec_enc(0)
{}

IWPixmap::Encode::~Encode()
{
  close_codec();
}

void
IWPixmap::Encode::init(const GPixmap &pm, const GP<GBitmap> gmask, CRCBMode crcbmode)
{
  /* Free */
  close_codec();
  delete ymap;
  delete cbmap;
  delete crmap;
  ymap = cbmap = crmap = 0;
  /* Create */
  int w = pm.columns();
  int h = pm.rows();
  signed char *buffer;
  GPBuffer<signed char> gbuffer(buffer,w*h);
  // Create maps
  Map::Encode *eymap = new Map::Encode(w,h);
  ymap = eymap;
  // Handle CRCB mode
  switch (crcbmode) 
    {
    case CRCBnone:   crcb_half=1; crcb_delay=-1; break;
    case CRCBhalf:   crcb_half=1; crcb_delay=10; break;        
    case CRCBnormal: crcb_half=0; crcb_delay=10; break;
    case CRCBfull:   crcb_half=0; crcb_delay= 0; break;
    }
  // Prepare mask information
  const signed char *msk8 = 0;
  int mskrowsize = 0;
  GBitmap *mask=gmask;
  if (mask)
  {
    msk8 = (signed char const *)((*mask)[0]);
    mskrowsize = mask->rowsize();
  }
  // Fill buffer with luminance information
  DJVU_PROGRESS_TASK(create,"initialize pixmap",3);
  DJVU_PROGRESS_RUN(create,(crcb_delay>=0 ? 1 : 3));
  Transform::Encode::RGB_to_Y(pm[0], w, h, pm.rowsize(), buffer, w);
  if (crcb_delay < 0)
    {
      // Stupid inversion for gray images
      signed char *e = buffer + w*h;
      for (signed char *b=buffer; b<e; b++)
        *b = 255 - *b;
    }
  // Create YMAP
  eymap->create(buffer, w, msk8, mskrowsize);
  // Create chrominance maps
  if (crcb_delay >= 0)
    {
      Map::Encode *ecbmap = new Map::Encode(w,h);
      cbmap = ecbmap;
      Map::Encode *ecrmap = new Map::Encode(w,h);
      crmap = ecrmap;
      // Process CB information
      DJVU_PROGRESS_RUN(create,2);
      Transform::Encode::RGB_to_Cb(pm[0], w, h, pm.rowsize(), buffer, w);
      ecbmap->create(buffer, w, msk8, mskrowsize);
      // Process CR information
      DJVU_PROGRESS_RUN(create,3);
      Transform::Encode::RGB_to_Cr(pm[0], w, h, pm.rowsize(), buffer, w); 
      ecrmap->create(buffer, w, msk8, mskrowsize);
      // Perform chrominance reduction (CRCBhalf)
      if (crcb_half)
        {
          ecbmap->slashres(2);
          ecrmap->slashres(2);
        }
    }
}

void 
IWPixmap::Encode::encode_iff(IFFByteStream &iff, int nchunks, const IWEncoderParms *parms)
{
  if (ycodec_enc)
    G_THROW( ERR_MSG("IW44Image.left_open3") );
  int flag = 1;
  iff.put_chunk("FORM:PM44", 1);
  DJVU_PROGRESS_TASK(iff,"encode pixmap chunk", nchunks);
  for (int i=0; flag && i<nchunks; i++)
    {
      DJVU_PROGRESS_RUN(iff,i+1);
      iff.put_chunk("PM44");
      flag = encode_chunk(iff.get_bytestream(), parms[i]);
      iff.close_chunk();
    }
  iff.close_chunk();
  close_codec();
}

void 
IWPixmap::Encode::close_codec(void)
{
  delete ycodec_enc;
  delete cbcodec_enc;
  delete crcodec_enc;
  ycodec_enc = crcodec_enc = cbcodec_enc = 0;
  IWPixmap::close_codec();
}

int  
IWPixmap::Encode::encode_chunk(GP<ByteStream> gbs, const IWEncoderParms &parm)
{
  // Check
  if (parm.slices==0 && parm.bytes==0 && parm.decibels==0)
    G_THROW( ERR_MSG("IW44Image.need_stop2") );
  if (!ymap)
    G_THROW( ERR_MSG("IW44Image.empty_object2") );
  // Open
  if (!ycodec_enc)
  {
    cslice = cserial = cbytes = 0;
    ycodec_enc = new Codec::Encode(*ymap);
    if (crmap && cbmap)
    {
      cbcodec_enc = new Codec::Encode(*cbmap);
      crcodec_enc = new Codec::Encode(*crmap);
    }
  }

  // Adjust cbytes
  cbytes += sizeof(struct IW44Image::PrimaryHeader);
  if (cserial == 0)
    cbytes += sizeof(struct IW44Image::SecondaryHeader) + sizeof(struct IW44Image::TertiaryHeader);
  // Prepare zcodec slices
  int flag = 1;
  int nslices = 0;
  GP<ByteStream> gmbs=ByteStream::create();
  ByteStream &mbs=*gmbs;
  DJVU_PROGRESS_TASK(chunk, "encode pixmap chunk", parm.slices-cslice);
  {
    float estdb = -1.0;
    GP<ZPCodec> gzp=ZPCodec::create(gmbs, true, true);
    ZPCodec &zp=*gzp;
    while (flag)
      {
        if (parm.decibels>0  && estdb>=parm.decibels)
          break;
        if (parm.bytes>0  && mbs.tell()+cbytes>=parm.bytes)
          break;
        if (parm.slices>0 && nslices+cslice>=parm.slices)
          break;
        DJVU_PROGRESS_RUN(chunk,(1+nslices-cslice)|0xf);
        flag = ycodec_enc->code_slice(zp);
        if (flag && parm.decibels>0)
          if (ycodec_enc->curband==0 || estdb>=parm.decibels-DECIBEL_PRUNE)
            estdb = ycodec_enc->estimate_decibel(db_frac);
        if (crcodec_enc && cbcodec_enc && cslice+nslices>=crcb_delay)
          {
            flag |= cbcodec_enc->code_slice(zp);
            flag |= crcodec_enc->code_slice(zp);
          }
        nslices++;
      }
  }
  // Write primary header
  struct IW44Image::PrimaryHeader primary;
  primary.serial = cserial;
  primary.slices = nslices;
  primary.encode(gbs);
  // Write secondary header
  if (cserial == 0)
    {
      struct IW44Image::SecondaryHeader secondary;
      secondary.major = IWCODEC_MAJOR;
      secondary.minor = IWCODEC_MINOR;
      if (! (crmap && cbmap))
        secondary.major |= 0x80;
      secondary.encode(gbs);
      struct IW44Image::TertiaryHeader tertiary;
      tertiary.xhi = (ymap->iw >> 8) & 0xff;
      tertiary.xlo = (ymap->iw >> 0) & 0xff;
      tertiary.yhi = (ymap->ih >> 8) & 0xff;
      tertiary.ylo = (ymap->ih >> 0) & 0xff;
      tertiary.crcbdelay = (crcb_half ? 0x00 : 0x80);
      tertiary.crcbdelay |= (crcb_delay>=0 ? crcb_delay : 0x00);
      tertiary.encode(gbs);
    }
  // Write slices
  mbs.seek(0);
  gbs->copy(mbs);
  // Return
  cbytes  += mbs.tell();
  cslice  += nslices;
  cserial += 1;
  return flag;
}

// code_slice
// -- read/write a slice of datafile

int
IW44Image::Codec::Encode::code_slice(ZPCodec &zp)
{
  // Check that code_slice can still run
  if (curbit < 0)
    return 0;
  // Perform coding
  if (! is_null_slice(curbit, curband))
    {
      for (int blockno=0; blockno<map.nb; blockno++)
        {
          const int fbucket = bandbuckets[curband].start;
          const int nbucket = bandbuckets[curband].size;
          encode_buckets(zp, curbit, curband, 
                         map.blocks[blockno], emap.blocks[blockno], 
                         fbucket, nbucket);
        }
    }
  return finish_code_slice(zp);
}



#ifdef HAVE_NAMESPACES
}
# ifndef NOT_USING_DJVU_NAMESPACE
using namespace DJVU;
# endif
#endif
#endif // NEED_DECODER_ONLY

