/*
 * Copyright (c) 1987, 1988, 1989, 1990, 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/*
 * X11-dependent raster code
 */

/*
 * $Id: xraster.C,v 1.12 1996/12/23 12:11:13 bmarsch Exp $
 *
 * $Log: xraster.C,v $
 * Revision 1.12  1996/12/23 12:11:13  bmarsch
 * Bugfix for arbitrary depths
 *
 * Revision 1.11  1996/07/26 11:36:45  bmarsch
 * Bugfix for Linux ELF
 *
 * Revision 1.10  1996/07/01 13:34:46  bmarsch
 * Bugfix in scaling
 *
 * Revision 1.9  1996/07/01 11:51:03  bmarsch
 * Bugfix in copy constructor
 *
 * Revision 1.8  1996/07/01 08:17:13  bmarsch
 * Implemented member setSize()
 *
 * Revision 1.7  1996/05/07 16:13:51  bmarsch
 * Replaced RGBAarray's with RGBA_BT_array's
 *
 * Revision 1.6  1996/03/06 14:11:56  bmarsch
 * Bugfix: initialize Floyd-Steinberg dithering
 *
 * Revision 1.5  1996/03/04 17:10:01  bmarsch
 * All color allocation code has been moved to xwindow.C
 *
 * Revision 1.4  1996/03/01 14:13:37  bmarsch
 * Extented alpha channel to [0;255]
 *
 * Revision 1.3  1996/02/15 17:21:56  bmarsch
 * Includes from OS/ were moved to hyperg/OS/
 *
 * Revision 1.2  1996/01/17 17:26:55  bmarsch
 * Removed some warnings
 *
 */

#include <stdlib.h>
#include <iostream.h>
#include <string.h>

#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/raster.h>
#include <InterViews/session.h>
#include <IV-X11/Xlib.h>
#include <IV-X11/Xutil.h>
#include <IV-X11/xdisplay.h>
#include <IV-X11/xraster.h>
#include <IV-X11/xwindow.h>
#include <IV-X11/harcolors.h>
#include <hyperg/OS/math.h>
#include <hyperg/OS/memory.h>


// ***********************************************************************

Raster::Raster(unsigned long ww, unsigned long hh,
               boolean doDither,
               long sw, long sh
)
{
  Display* d = Session::instance()->default_display();

  int w = (int) ww;
  int h = (int) hh;

  rep_ = nil;
  modified_ = false;
  pwidth_ = w;
  pheight_ = h;
  width_ = d->to_coord(w);
  height_ = d->to_coord(h);
  swidth_ = sw < 0 ? w : (unsigned int) sw;
  sheight_ = sh < 0 ? h : (unsigned int) sh;
  scwidth_ = d->to_coord(swidth_);
  scheight_ = d->to_coord(sheight_);
  left_ = 0;
  right_ = scwidth_;
  bottom_ = 0;
  top_ = scheight_;

  doDither_ = doDither;

  unsigned int len = w*h*3;
  data_ = new unsigned char[len];
  Memory::zero(data_, len);
  alpha_ = nil;
  data_rgba_ = nil;
}

Raster::Raster(const Raster& raster) {
  raster.flush();
  
  rep_ = nil;   // rep_ is built automatically in flush()

  modified_ = true;
  width_ = raster.width_;
  height_ = raster.height_;
  pwidth_ = raster.pwidth_;
  pheight_ = raster.pheight_;
  left_ = raster.left_;
  right_ = raster.right_;
  bottom_ = raster.bottom_;
  top_ = raster.top_;

  doDither_ = raster.doDither_;

  swidth_ = raster.swidth_;
  sheight_ = raster.sheight_;
  scwidth_ = raster.scwidth_;
  scheight_ = raster.scheight_;

  unsigned int alpha_len = pwidth_ * pheight_;
  unsigned int len = alpha_len * 3;
  data_ = new unsigned char[len];
  Memory::copy(raster.data_, data_, len);
  if (raster.alpha_) {
    useAlphaTransparency();
    Memory::copy(raster.alpha_, alpha_, alpha_len);
  }
  else
    alpha_ = nil;
  data_rgba_ = nil;
}

//Raster::Raster(RasterRep* r) { rep_ = r; }

Raster::~Raster() {
  delete[] data_;
  delete[] alpha_;
  delete rep_;
}

Coord Raster::width() const { return scwidth_; }
Coord Raster::height() const { return scheight_; }
unsigned long Raster::pwidth() const { return swidth_; }
unsigned long Raster::pheight() const { return sheight_; }

Coord Raster::left_bearing() const { return -left_; }
Coord Raster::right_bearing() const { return right_; }
Coord Raster::ascent() const { return top_; }
Coord Raster::descent() const { return -bottom_; }

unsigned long Raster::unscaledWidth() const { return pwidth_; }
unsigned long Raster::unscaledHeight() const { return pheight_; }

void Raster::setSize(long scaledWidth, long scaledHeight)
{
  if (swidth_ == scaledWidth && sheight_ == scaledHeight)
    return;   // size hasn't changed

  Display* d = Session::instance()->default_display();
  swidth_ = scaledWidth;
  sheight_ = scaledHeight;
  scwidth_ = d->to_coord(scaledWidth);
  scheight_ = d->to_coord(scaledHeight);
  left_ = 0;
  right_ = scwidth_;
  bottom_ = 0;
  top_ = scheight_;

  modified_ = true;
  if (rep_) {
    delete rep_;
    rep_ = nil;
  }
}

void Raster::peek(unsigned long x, unsigned long y,
                  ColorIntensity& red, ColorIntensity& green, ColorIntensity& blue,
                  float& alpha) const
{
  unsigned char r, g, b, al;
  peekChr (x, y, r, g, b, al);
  red = float(r) / 0xff;
  green = float(g) / 0xff;
  blue = float(b) / 0xff;
  alpha = float(al) / 0xff;
}

void Raster::peekChr(unsigned long x, unsigned long y,
                     unsigned char& red, unsigned char& green, unsigned char& blue,
                     unsigned char& alpha) const 
{
  unsigned int line = pheight_ - (unsigned int) y - 1;
  unsigned char* cptr = data_ + (line * pwidth_ + x) * 3;
  red = *cptr++;
  green = *cptr++;
  blue = *cptr;
  // get alpha channel
  if (alpha_) {
    unsigned char* ptr = alpha_ + (line * pwidth_ + x);
    alpha = *ptr;
  }
  else
    alpha = 0xFF;
}

void Raster::poke(unsigned long x, unsigned long y,
                  ColorIntensity r, ColorIntensity g, ColorIntensity b, float alpha)
{
  pokeChr (
    x, y,
    (unsigned char) (r * 0xff),
    (unsigned char) (g * 0xff),
    (unsigned char) (b * 0xff),
    (unsigned char) (alpha * 0xff)
  );
}


void Raster::pokeChr(unsigned long x, unsigned long y,
                     unsigned char red, unsigned char green, unsigned char blue,
                     unsigned char alpha)
{
  unsigned int line = pheight_ - (unsigned int) y - 1;
  unsigned char* cptr = data_ + (line * pwidth_ + x) * 3;
  *cptr++ = red;
  *cptr++ = green;
  *cptr = blue;
  if (alpha_) {
    // set alpha channel
    unsigned char* ptr = alpha_ + (line * pwidth_ + x);
    *ptr = alpha;
  }
  modified_ = true;
}

// bit-masking table for alpha channel
static const int mask[] = {
  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};

void Raster::flush() const {
  Raster* This = (Raster*) this;  // need to cast away const

  if (!This->rep_)
    This->rep_ = new RasterRep(this);
  RasterRep* r = This->rep_;

  if (modified_) {
    DisplayRep* dr = r->display_->rep();
    XDisplay* xdpy = dr->display_;

    rep_->ditherImage(doDither_);

    if (alpha_)
      rep_->makeTransparent();

    XPutImage(
      xdpy, r->pixmap_, r->gc_, r->image_,
      0, 0, 0, 0, swidth_, sheight_
    );

    // clean up image data (was allocated in ditherImage()
    delete[] r->image_->data;
    r->image_->data = nil;

    This->modified_ = false;
  }
}


// bmarsch: 20 Dec 94: transparency

void Raster::useAlphaTransparency()
{
  if (!alpha_) {
    int len = pheight_ * pwidth_;
    alpha_ = new unsigned char[len];
    memset(alpha_, 0xff, len);
    modified_ = true;
  }
}

// bmarsch: 09 Jan 95: return data array
const unsigned char* Raster::getRGBarray()
{
  return data_;
}

void Raster::freeRGBarray() {}

const unsigned char* Raster::getRGBA_BT_array()
{
  // copy to one array (from bottom to top!)
  if (!data_rgba_) {
    int w = pwidth_;
    int h = pheight_;
    unsigned char* rgb = new unsigned char[w * h * 4];
    unsigned char* dest = rgb + w * (h-1) * 4;
    unsigned char* dptr = data_;
    if (!alpha_) useAlphaTransparency();
    unsigned char* aptr = alpha_;

    for (int i = 0; i < h; i++) {
      for (int j = 0; j < w; j++) {
        *dest++ = *dptr++;  // copy r
        *dest++ = *dptr++;  // copy g
        *dest++ = *dptr++;  // copy b
        *dest++ = *aptr++;  // copy alpha
      }
      dest -= 8 * w;   // 2*w * 4
    }

    data_rgba_ = rgb;
  }

  return data_rgba_;
}

void Raster::freeRGBA_BT_array()
{
  delete[] data_rgba_;
  data_rgba_ = nil;
}

// ***********************************************************************
// RasterRep

// forward declaration
static void initDither();

int RasterRep::image_byte_order_;
boolean RasterRep::colors_allocated_ = false;

RasterRep::RasterRep(const Raster* raster)
{
  Display* d = Session::instance()->default_display();
  display_ = d;
  raster_ = raster;

  DisplayRep* dr = display_->rep();
  XDisplay* dpy = dr->display_;
  wvis_ = dr->default_visual_;

  image_byte_order_ = ImageByteOrder(dpy);

  int vclass = wvis_->visual()->c_class;
  int depth = wvis_->depth();
  if (vclass == PseudoColor)
    rvis_ = (depth == 8) ? Pseudo8 : Pseudo_Other;
  else if (vclass == TrueColor || vclass == DirectColor)
    rvis_ = (depth == 24) ? True24 : True_Other;

  pwidth_ = raster->swidth_;
  pheight_ = raster->sheight_;
  width_ = raster->scwidth_;
  height_ = raster->scheight_;
  left_ = 0;
  right_ = width_;
  bottom_ = 0;
  top_ = height_;

  // init scaling
  float sx = (float)raster->pwidth_ / (float)pwidth_;
  float sy = (float)raster->pheight_ / (float)pheight_;
  diffx_ = new int[pwidth_];
  diffy_ = new int[pheight_];
  int i;
  for (i = 0; i < pwidth_-1; i++)
    diffx_[i] = (int((i+1)*sx) - int(i*sx) - 1) * 3;
  diffx_[pwidth_-1] = (raster->pwidth_ - int((pwidth_-1)*sx) - 1) * 3;
  for (i = 0; i < pheight_-1; i++)
    diffy_[i] = (int((i+1)*sy) - int(i*sy) - 1) * 3;
  diffy_[pheight_-1] = (raster->pheight_ - int((pheight_-1)*sy) - 1) * 3;

  // allocate Harmony Colormap
  if (!colors_allocated_) {
    enableHarmonyColors();
    wvis_->init_color_tables();
    colors_allocated_ = true;
    initDither();
  }

  pixmap_ = XCreatePixmap(dpy, dr->root_,
                          pwidth_, pheight_,
                          depth);
  gc_ = XCreateGC(dpy, pixmap_, 0, nil);

  // use XCreateImage to be able to set bitmap_pad manually
  image_ = XCreateImage(
    dpy,                // display
    wvis_->visual(),    // visual
    depth,              // depth
    ZPixmap,            // format
    0,                  // offset
    nil,                // data
    pwidth_, pheight_,  // width, height
    8,                  // bitmap_pad
    0                   // bytes_per_line
  );

  clip_valid_ = false;
}

RasterRep::~RasterRep()
{
  XDestroyImage(image_);

  XDisplay* dpy = display_->rep()->display_;
  XFreePixmap(dpy, pixmap_);
  XFreeGC(dpy, gc_);

  if (clip_valid_)
    XFreePixmap(dpy, clip_);

  delete[] diffx_;
  delete[] diffy_;
}

// ***********************************************************************
// transparency

void RasterRep::makeTransparent() const
{
  if (clip_valid_)
    XFreePixmap(display_->rep()->display_, clip_);

  /* make bit array out of alpha channel */
  int width = (pwidth_ + 7) / 8;
  int blen = width * pheight_;
  char* bitmap = new char[blen];
  Memory::zero(bitmap, blen);
  char* dptr = bitmap;

  int diff = width * 8 - pwidth_;

  unsigned char* sptr = raster_->alpha_;
  for (int i = 0; i < pheight_; i++) {
    for (int j = 0; j < pwidth_;) {
      if (*sptr++)
        *dptr |= mask[j%8];
      sptr += diffx_[j] / 3; 
      if (++j % 8 == 0)
        dptr++;
    }
    if (diff > 0)
      dptr++;
    sptr += diffy_[i] / 3 * raster_->pwidth_;
  }

//   for (int i = 0; i < pheight_; i++) {
//     int j;
//     for (j = 0; j < width-1; j++) {
//       int dx = diffx_[j] / 3;
//       if (*a++) *b |= 0x01;
//       a += dx;
//       if (*a++) *b |= 0x02;
//       a += dx;
//       if (*a++) *b |= 0x04;
//       a += dx;
//       if (*a++) *b |= 0x08;
//       a += dx;
//       if (*a++) *b |= 0x10;
//       a += dx;
//       if (*a++) *b |= 0x20;
//       a += dx;
//       if (*a++) *b |= 0x40;
//       a += dx;
//       if (*a++) *b |= 0x80;
//       b++;
//     }
//     for (j = 0; j < diff; j++) {
//       if (*a++) *b |= mask[j];
//       a += diffx_[j] / 3;
//     }
//     if (diff > 0)
//       b++;
//     a += diffy_[i] / 3;
//   }

  // cast away const
  RasterRep* This = (RasterRep*) this;
  This->clip_ = XCreateBitmapFromData(
    display_->rep()->display_,
    display_->rep()->root_,
    bitmap,
    width * 8, pheight_
  );
  delete[] bitmap;
  This->clip_valid_ = true;
}

// ***********************************************************************
// bmarsch: 10 Nov 94
// initialization of fs dithering (copied from xv)

static int tbl1[512],     /* tables used in F-S Dithering */
           tbl3[512],     /* contain i/16, 3i/16, 5i/16, 7i/16, */
           tbl5[512],     /* (i=-256..255) respectively */
           tbl7[512];

static void initDither()
{
  /* initialize Floyd-Steinberg division tables */
  /* make sure rounding is done correctly for negative values! */

  int i;

  for (i = -256; i < 0; i++) {
    tbl1[i+256] = -((8 -i  )/16);
    tbl3[i+256] = -((8 -3*i)/16);
    tbl5[i+256] = -((8 -5*i)/16);
    tbl7[i+256] = -((8 -7*i)/16);
  }

  for (i = 0; i < 256; i++) {
    tbl1[i+256] = (i  +8)/16;
    tbl3[i+256] = (3*i+8)/16;
    tbl5[i+256] = (5*i+8)/16;
    tbl7[i+256] = (7*i+8)/16;
  }
}

// ***********************************************************************
// bmarsch: 10 Nov 94
// new methods for dithering

void RasterRep::ditherImage(boolean doDither) const
{
  long imagesize = raster_->pwidth()*raster_->pheight();
  switch (rvis_) {
    case True24:   // TrueColor/DirectColor 24 bit
      image_->data = new char[imagesize*4];
      trueColorDither();
      break;

    case Pseudo8:  // PseudoColor 8 bit
      image_->data = new char[imagesize];
      if (doDither)
        fsDither();   // floyd-steinberg dithering
      else
        noDither();   // no dithering
      break;

    case Pseudo_Other:   // any other visual
    case True_Other:
      image_->data = new char[imagesize * ((wvis_->depth()+7)>>3)];
      if (doDither)
        slowfsDither();
      else
        slownoDither();
      break;
  }
}

void RasterRep::trueColorDither() const
{
  unsigned char* sptr = (unsigned char*) raster_->data();
  char* dptr = image_->data;

  if (image_byte_order_ == LSBFirst) {
    char r, g, b;
    for (int i = 0; i < pheight_; i++) {
      for (int j = 0; j < pwidth_; j++) {
        r = (char) *sptr++;
        g = (char) *sptr++;
        b = (char) *sptr++;
        *dptr++ = b;
        *dptr++ = g;
        *dptr++ = r;
        *dptr++ = 0;
        sptr += diffx_[j];
      }
      sptr += diffy_[i] * raster_->pwidth_;
    }
  }
  else {
    char r, g, b;
    for (int i = 0; i < pheight_; i++) {
      for (int j = 0; j < pwidth_; j++) {
        r = (char) *sptr++;
        g = (char) *sptr++;
        b = (char) *sptr++;
        *dptr++ = 0;
        *dptr++ = b;
        *dptr++ = g;
        *dptr++ = r;
        sptr += diffx_[j];
      }
      sptr += diffy_[i] * raster_->pwidth_;
    }
  }
}

inline static unsigned long getzeros(unsigned long mask)
{
  unsigned long c = 0;
  while ((mask & 0x1) == 0 && c < sizeof(unsigned long)*8) {
    c++;
    mask >>= 1;
  }
  return c;
}

inline static unsigned long getones(unsigned long mask)
{
  unsigned long c = 0;
  while ((mask & 0x1) == 0 && c < sizeof(unsigned long)*8)
    mask >>= 1;

  while ((mask & 0x1) == 1 && c < sizeof(unsigned long)*8) {
    c++;
    mask >>= 1;
  }
  return c;
}

void RasterRep::noDither() const
{  
  unsigned char* sptr = (unsigned char*) raster_->data();
  char* dptr = image_->data;

  unsigned char r, g, b;
  for (int i = 0; i < pheight_; i++) {
    for (int j = 0; j < pwidth_; j++) {
      r = *sptr++;
      g = *sptr++;
      b = *sptr++;
      // this maptable only considers 4 bits for each r, g and b
      int ind = HarGetColorIndex_256(r, g, b);
      *dptr++ = (char) harmony_pixel[ind];
      sptr += diffx_[j];
    }
    sptr += diffy_[i] * raster_->pwidth_;
  }
}

void RasterRep::slownoDither() const
{  
  unsigned char* sptr = (unsigned char*) raster_->data();

  unsigned char r, g, b;
  for (int i = 0; i < pheight_; i++) {
    for (int j = 0; j < pwidth_; j++) {
      r = *sptr++;
      g = *sptr++;
      b = *sptr++;
      // this maptable only considers 4 bits for each r, g and b
      int ind = HarGetColorIndex_256(r, g, b);
      XPutPixel(image_, j, i, harmony_pixel[ind]);
      sptr += diffx_[j];
    }
    sptr += diffy_[i] * raster_->pwidth_;
  }
}


#define CONSTRAIN(x) \
  if (x < 0) x = 0; \
  else if (x > 255) x = 255;

//static unsigned char rmap[256], gmap[256], bmap[256];


void RasterRep::fsDither() const
{
  // data pointer
  unsigned char* data = (unsigned char*) raster_->data();
  char* idata = image_->data;

  // error values
  int er = 0, eg = 0, eb = 0;
  int* last_error_r = new int[pwidth_];
  int* last_error_g = new int[pwidth_];
  int* last_error_b = new int[pwidth_];
  Memory::zero(last_error_r, pwidth_*sizeof(int));
  Memory::zero(last_error_g, pwidth_*sizeof(int));
  Memory::zero(last_error_b, pwidth_*sizeof(int));
  int* new_error_r = new int[pwidth_];
  int* new_error_g = new int[pwidth_];
  int* new_error_b = new int[pwidth_];

  int r, g, b;
  for (int i = 0; i < pheight_; i++) {

    int* error_r = last_error_r;
    int* error_g = last_error_g;
    int* error_b = last_error_b;
    int* next_error_r = new_error_r;
    int* next_error_g = new_error_g;
    int* next_error_b = new_error_b;
    Memory::zero(next_error_r, pwidth_*sizeof(int));
    Memory::zero(next_error_g, pwidth_*sizeof(int));
    Memory::zero(next_error_b, pwidth_*sizeof(int));

    for (int j = 0; j < pwidth_; j++) {
      // get r, g, b
      r = *data++;
      g = *data++;
      b = *data++;
      // add error values
      r += *error_r++ + er;
      g += *error_g++ + eg;
      b += *error_b++ + eb;
      // constrain to [0..255]
      CONSTRAIN(r);
      CONSTRAIN(g);
      CONSTRAIN(b);

      // choose pixel value
      unsigned short r1, g1, b1;
      int ind = HarGetColorIndex_256(r, g, b);
      *idata++ = (char) harmony_pixel[ind];
      r1 = harmony_color[ind].r;
      g1 = harmony_color[ind].g;
      b1 = harmony_color[ind].b;

      // compute new color errors
      r -= r1 >> 8;
      g -= g1 >> 8;
      b -= b1 >> 8;
      r += 256;
      g += 256;
      b += 256;
      // distribute error
      if (j != pwidth_) {  // adjust RIGHT pixel
        er = tbl7[r];
        eg = tbl7[g];
        eb = tbl7[b];
      }

      next_error_r[0] += tbl5[r];
      next_error_g[0] += tbl5[g];
      next_error_b[0] += tbl5[b];

      if (j > 0) {  // do BOTTOM LEFT pixel
        next_error_r[-1] += tbl3[r];
        next_error_g[-1] += tbl3[g];
        next_error_b[-1] += tbl3[b];
      }

      if (j != pwidth_-1) {  // do BOTTOM RIGHT pixel
        next_error_r[1] += tbl1[r];
        next_error_g[1] += tbl1[g];
        next_error_b[1] += tbl1[b];
      }
 
      next_error_r++;
      next_error_g++;
      next_error_b++;
      data += diffx_[j];
    }

    // switch last_error_[rgb]_ and new_error_[rgb]
    int* tmp = last_error_r;
    last_error_r = new_error_r;
    new_error_r = tmp;
    tmp = last_error_g;
    last_error_g = new_error_g;
    new_error_g = tmp;
    tmp = last_error_b;
    last_error_b = new_error_b;
    new_error_b = tmp;

    data += diffy_[i] * raster_->pwidth_;
  }

  delete[] last_error_r;
  delete[] last_error_g;
  delete[] last_error_b;
  delete[] new_error_r;
  delete[] new_error_g;
  delete[] new_error_b;
}

void RasterRep::slowfsDither() const
{
  // data pointer
  unsigned char* data = (unsigned char*) raster_->data();

  // error values
  int er = 0, eg = 0, eb = 0;
  int* last_error_r = new int[pwidth_];
  int* last_error_g = new int[pwidth_];
  int* last_error_b = new int[pwidth_];
  Memory::zero(last_error_r, pwidth_*sizeof(int));
  Memory::zero(last_error_g, pwidth_*sizeof(int));
  Memory::zero(last_error_b, pwidth_*sizeof(int));
  int* new_error_r = new int[pwidth_];
  int* new_error_g = new int[pwidth_];
  int* new_error_b = new int[pwidth_];

  int r, g, b;
  for (int i = 0; i < pheight_; i++) {

    int* error_r = last_error_r;
    int* error_g = last_error_g;
    int* error_b = last_error_b;
    int* next_error_r = new_error_r;
    int* next_error_g = new_error_g;
    int* next_error_b = new_error_b;
    Memory::zero(next_error_r, pwidth_*sizeof(int));
    Memory::zero(next_error_g, pwidth_*sizeof(int));
    Memory::zero(next_error_b, pwidth_*sizeof(int));

    for (int j = 0; j < pwidth_; j++) {
      // get r, g, b
      r = *data++;
      g = *data++;
      b = *data++;
      // add error values
      r += *error_r++ + er;
      g += *error_g++ + eg;
      b += *error_b++ + eb;
      // constrain to [0..255]
      CONSTRAIN(r);
      CONSTRAIN(g);
      CONSTRAIN(b);

      // choose pixel value
      unsigned short r1, g1, b1;
      int ind = HarGetColorIndex_256(r, g, b);
      XPutPixel(image_, j, i, harmony_pixel[ind]);
      r1 = harmony_color[ind].r;
      g1 = harmony_color[ind].g;
      b1 = harmony_color[ind].b;

      // compute new color errors
      r -= r1 >> 8;
      g -= g1 >> 8;
      b -= b1 >> 8;
      r += 256;
      g += 256;
      b += 256;
      // distribute error
      if (j != pwidth_) {  // adjust RIGHT pixel
        er = tbl7[r];
        eg = tbl7[g];
        eb = tbl7[b];
      }

      next_error_r[0] += tbl5[r];
      next_error_g[0] += tbl5[g];
      next_error_b[0] += tbl5[b];

      if (j > 0) {  // do BOTTOM LEFT pixel
        next_error_r[-1] += tbl3[r];
        next_error_g[-1] += tbl3[g];
        next_error_b[-1] += tbl3[b];
      }

      if (j != pwidth_-1) {  // do BOTTOM RIGHT pixel
        next_error_r[1] += tbl1[r];
        next_error_g[1] += tbl1[g];
        next_error_b[1] += tbl1[b];
      }
 
      next_error_r++;
      next_error_g++;
      next_error_b++;
      data += diffx_[j];
    }

    // switch last_error_[rgb]_ and new_error_[rgb]
    int* tmp = last_error_r;
    last_error_r = new_error_r;
    new_error_r = tmp;
    tmp = last_error_g;
    last_error_g = new_error_g;
    new_error_g = tmp;
    tmp = last_error_b;
    last_error_b = new_error_b;
    new_error_b = tmp;

    data += diffy_[i] * raster_->pwidth_;
  }

  delete[] last_error_r;
  delete[] last_error_g;
  delete[] last_error_b;
  delete[] new_error_r;
  delete[] new_error_g;
  delete[] new_error_b;
}

#undef CONSTRAIN
