/* xsane -- a graphical (X11, gtk) scanner-oriented SANE frontend

   xsane-save.c

   Oliver Rauch <Oliver.Rauch@rauch-domain.de>
   Copyright (C) 1998-2005 Oliver Rauch
   This file is part of the XSANE package.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */ 

/* ---------------------------------------------------------------------------------------------------------------------- */

#include "xsane.h"
#include "xsane-back-gtk.h"
#include "xsane-front-gtk.h"
#include "xsane-save.h"
#include <time.h>
#include <sys/wait.h> 

/* the following test is always false */
#ifdef _native_WIN32
# include <winsock.h>
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <netdb.h>
#endif

#ifdef HAVE_LIBJPEG
#include <jpeglib.h>
#endif

#ifdef HAVE_LIBZ
#include <zlib.h>
#endif

#ifdef HAVE_LIBPNG
#include <png.h>
#endif

#ifdef HAVE_LIBTIFF
#include <tiffio.h>
#endif

#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif

#ifdef HAVE_OS2_H
#include <process.h>
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_ANY_GIMP
 
#include <libgimp/gimp.h>
 
static void xsane_gimp_query(void);
#ifdef HAVE_GIMP_2
static void xsane_gimp_run(const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals);
#else
static void xsane_gimp_run(char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals);
#endif
 
GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,                         /* init_proc */
  NULL,                         /* quit_proc */
  xsane_gimp_query,             /* query_proc */
  xsane_gimp_run,               /* run_proc */
};


static int xsane_decode_devname(const char *encoded_devname, int n,
char *buf);
static int xsane_encode_devname(const char *devname, int n, char *buf);
void null_print_func(gchar *msg);
 
#endif /* HAVE_ANY_GIMP */

/* ---------------------------------------------------------------------------------------------------------------------- */
/* why this routine ? 
 Problem: link attack
          Bad user wants to overwrite a file (mywork.txt) of good user.
          File permissions of mywork.txt is 700 so that bad user can not
          change or overwrite the file. Directory permissions allow bad user
          to write into directory. Bad user sets symlink from a file that good
          user will write soon (image.pnm) to mywork.txt.
          ==> Good user overwrites his own file, he is allowed to do so.

 Solution: remove file.
           Create outputfile and make sure that it does not exist while creation.

           The file is created with the requested image-file permissions.

 Note: This case is a bit curious because it is only a small part of a larger problem:
 When other users have write access to the directory they simply can move
 mywork.txt to image.pnm. If they do it in the right moment the file is
 overwritten without any notice of good user. If they do it long before xsane
 wants to write image.pnm then xsane will possibly ask if image.pnm shall be
 overwritten. So the real solution is to make the direcoty permissions safe!!!
 But some users asked for this and so I added this.


 This routine shall not be called for temporary files because temp files shall not
 be removed after they have been created safe. (Although a temporary file should
 not be a symlink so there should be no problem with this)
*/

int xsane_create_secure_file(const char *filename)
/* returns 0 on success, -1 on error */
{
 int fd;

  DBG(DBG_proc, "xsane_create_secure_file\n");

  remove(filename); /* we need to remove the file because open(..., O_EXCL) will fail otherwise */
  umask((mode_t) preferences.image_umask); /* define image file permissions */   
  fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
  umask(XSANE_DEFAULT_UMASK); /* define new file permissions */   

  if (fd > 0)
  {
    DBG(DBG_info, "file %s is created and secure\n", filename);
    close(fd);
    fd = 0;
  }
  else
  {
    DBG(DBG_info, "could not create secure file %s\n", filename);
  }

 return fd; /* -1 means file is not safe !!! otherwise 0 */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_cancel_save(int *cancel_save)
{
  DBG(DBG_proc, "xsane_cancel_save\n");
  *cancel_save = 1;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_convert_text_to_filename(char **text)
{
  DBG(DBG_proc, "xsane_convert_text_to_filename\n");

  if (text)
  {
   char *filename = *text;
   char buf[TEXTBUFSIZE];
   int buflen=0;
   int txtlen=0;

    while((filename[txtlen] != 0) && (buflen<253))
    {
      switch (filename[txtlen])
      {
        case ' ':
          buf[buflen++] = ':';
          buf[buflen++] = '_';
          txtlen++;
          break;

        case '/':
          buf[buflen++] = ':';
          buf[buflen++] = '%';
          txtlen++;
          break;

        case '*':
          buf[buflen++] = ':';
          buf[buflen++] = '#';
          txtlen++;
          break;

        case '?':
          buf[buflen++] = ':';
          buf[buflen++] = 'q';
          txtlen++;
          break;

        case '\\':
          buf[buflen++] = ':';
          buf[buflen++] = '=';
          txtlen++;
          break;

        case ';':
          buf[buflen++] = ':';
          buf[buflen++] = '!';
          txtlen++;
          break;

        case '&':
          buf[buflen++] = ':';
          buf[buflen++] = '+';
          txtlen++;
          break;

        case '<':
          buf[buflen++] = ':';
          buf[buflen++] = 's';
          txtlen++;
          break;

        case '>':
          buf[buflen++] = ':';
          buf[buflen++] = 'g';
          txtlen++;
          break;

        case '|':
          buf[buflen++] = ':';
          buf[buflen++] = 'p';
          txtlen++;
          break;

        case ':':
          buf[buflen++] = ':';
          buf[buflen++] = ':';
          txtlen++;
          break;

        default:
          buf[buflen++] = filename[txtlen++];
          break;
      }
    }
    buf[buflen] = 0;
    free(filename);
    *text = strdup(buf);
    DBG(DBG_info, "filename = \"%s\"\n", *text);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_get_filesize(char *filename)
{
 FILE *infile;
 int pos;
 int size;

  infile = fopen(filename, "rb"); /* read binary (b for win32) */
  if (infile == NULL)
  {
   return 0;
  }

  pos = ftell(infile);
  fseek(infile, 0, SEEK_END); /* get size */
  size = ftell(infile);
  fseek(infile, pos, SEEK_SET); /* go to previous position */

  fclose(infile);

 return size;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_ensure_counter_in_filename(char **filename, int counter_len)
{
 char *position_point = NULL;
 char *position;
 int counter = 1;

  DBG(DBG_proc, "xsane_ensure_counter_in_filename\n");

  if (!counter_len)
  {
   counter_len = 1;
  }

  position_point = strrchr(*filename, '.');

  if (!position_point) /* nothing usable ? */
  {
    position_point = *filename + strlen(*filename); /* position_point - 1 is last character */
  }

  if (position_point)
  {
    position = position_point-1;
    if ( (position < *filename) || (*position < '0') || (*position >'9') ) /* we have no counter */
    {
     char buf[PATH_MAX];
     int len;

      len = position_point - (*filename); /* length until "." or end of string */
      strncpy(buf, *filename, len);
      snprintf(buf+len, sizeof(buf)-len, "-%0*d%s", counter_len,  counter, position_point);
      *filename = strdup(buf);
    }
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_update_counter_in_filename(char **filename, int skip, int step, int min_counter_len)
{
 FILE *testfile;
 char *position_point = NULL;
 char *position_counter;
 char buf[PATH_MAX];
 int counter;
 int counter_len;
 int set_counter_len = min_counter_len;

  DBG(DBG_proc, "xsane_update_counter_in_filename\n");

  if ( (!step) && (!min_counter_len) )
  {
    return; /* do not touch counter */
  }

  while (1) /* loop because we may have to skip existing files */
  {
    position_point = strrchr(*filename, '.');

    if (!position_point) /* nothing usable ? */
    {
      position_point = *filename + strlen(*filename); /* here is no point, but position - 1 is last character */
    }

    if (position_point)
    {
      position_counter = position_point-1; /* go to last number of counter (if counter exists) */

      /* search non numeric char */
      while ( (position_counter >= *filename) && (*position_counter >= '0') && (*position_counter <='9') )
      {
        position_counter--; /* search fisrt numeric character */
      }

      position_counter++; /* go to first numeric charcter */

      counter_len = position_point - position_counter;

      if (counter_len) /* we have a counter */
      {
        sscanf(position_counter, "%d", &counter);
        counter = counter + step; /* update counter */

        if (counter < 0)
        {
          counter = 0;
          xsane_back_gtk_warning(WARN_COUNTER_UNDERRUN, TRUE);
          break; /* last available number ("..999") */
        }

        *position_counter = 0; /* set end of string mark to counter start */

        if (set_counter_len == 0)
        {
           set_counter_len = counter_len;
        }

        snprintf(buf, sizeof(buf), "%s%0*d%s", *filename, set_counter_len,  counter, position_point);

        DBG(DBG_info, "filename = \"%s\"\n", buf);

        free(*filename);
        *filename = strdup(buf);

        if (skip) /* test if filename already used */
        {
          if (preferences.filetype) /* add filetype to filename */
          {
            snprintf(buf, sizeof(buf), "%s%s", *filename, preferences.filetype);
            testfile = fopen(buf, "rb"); /* read binary (b for win32) */
          }
          else /* filetype in filename */
          {
            testfile = fopen(*filename, "rb"); /* read binary (b for win32) */
          }

          if (testfile) /* filename used: skip */
          {
            fclose(testfile);
          }
          else
          {
            break; /* filename not used, ok */
          }
        }
        else /* do not test if filename already used */
        {
          break; /* filename ok */
        }
      }
      else /* no counter */
      {
        break; /* no counter */
      }
    }
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_read_pnm_header(FILE *file, Image_info *image_info)
{
 int max_val, filetype_nr;
 char buf[TEXTBUFSIZE];

  fgets(buf, sizeof(buf)-1, file);
  DBG(DBG_info, "filetype header :%s", buf);
 
  if (buf[0] == 'P')
  {
    filetype_nr = atoi(buf+1); /* get filetype number */

    image_info->resolution_x = 72.0;
    image_info->resolution_y = 72.0;
    image_info->reduce_to_lineart = FALSE;

    while (strcmp(buf, "# XSANE data follows\n"))
    {
      fgets(buf, sizeof(buf)-1, file);

      if (!strncmp(buf, "#  resolution_x    =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->resolution_x);
      }
      else if (!strncmp(buf, "#  resolution_y    =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->resolution_y);
      }
      else if (!strncmp(buf, "#  threshold       =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->threshold);
      }
      else if (!strncmp(buf, "#  gamma           =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->gamma);
      }
      else if (!strncmp(buf, "#  gamma      IRGB =", 20))
      {
        sscanf(buf+20, "%lf %lf %lf %lf",
                        &image_info->gamma,
                             &image_info->gamma_red, 
                                &image_info->gamma_green, 
                                    &image_info->gamma_blue);
      }
      else if (!strncmp(buf, "#  brightness      =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->brightness);
      }
      else if (!strncmp(buf, "#  brightness IRGB =", 20))
      {
        sscanf(buf+20, "%lf %lf %lf %lf",
                        &image_info->brightness,
                             &image_info->brightness_red, 
                                &image_info->brightness_green, 
                                    &image_info->brightness_blue);
      }
      else if (!strncmp(buf, "#  contrast        =", 20))
      {
        sscanf(buf+20, "%lf", &image_info->contrast);
      }
      else if (!strncmp(buf, "#  contrast   IRGB =", 20))
      {
        sscanf(buf+20, "%lf %lf %lf %lf",
                        &image_info->contrast,
                             &image_info->contrast_red, 
                                &image_info->contrast_green, 
                                    &image_info->contrast_blue);
      }
      else if (!strncmp(buf, "#  reduce to lineart", 20))
      {
        image_info->reduce_to_lineart = TRUE;
      }
    }

    fscanf(file, "%d %d", &image_info->image_width, &image_info->image_height);

    image_info->depth = 1;

    if (filetype_nr != 4) /* P4 = lineart */
    {
      fscanf(file, "%d", &max_val);

      if (max_val == 255)
      {
        image_info->depth = 8;
      }
      else if (max_val == 65535)
      {
        image_info->depth = 16;
      }
    }

    fgetc(file); /* read exactly one newline character */
    

    image_info->colors = 1;
 
    if (filetype_nr == 6) /* ppm RGB */
    {
      image_info->colors = 3;
    } 
  }
#ifdef SUPPORT_RGBA
  else if (buf[0] == 'S') /* RGBA format */
  {
    fscanf(file, "%d %d\n%d", &image_info->image_width, &image_info->image_height, &max_val);
    fgetc(file); /* read exactly one newline character */

    image_info->depth = 1;

    if (max_val == 255)
    {
      image_info->depth = 8;
    }
    else if (max_val == 65535)
    {
      image_info->depth = 16;
    }

    image_info->colors = 4;
  }
#endif

  DBG(DBG_info, "xsane_read_pnm_header: width=%d, height=%d, depth=%d, colors=%d, resolution_x=%f, resolution_y=%f\n",
      image_info->image_width, image_info->image_height, image_info->depth, image_info->colors,
      image_info->resolution_x, image_info->resolution_y);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_write_pnm_header(FILE *file, Image_info *image_info, int save_pnm16_as_ascii)
{
 int maxval;
 int magic;

  fflush(file);
  rewind(file);

  if (image_info->depth > 8)
  {
    maxval = 65535;

    if (save_pnm16_as_ascii)
    {
      magic = 2; /* thats the magic number for grayscale ascii, 3 = color ascii */
    }
    else /* save pnm as binary */
    {
      magic = 5; /* that is the magic number for grayscake binary, 6 = color binary */
    }
  }
  else
  {
    maxval = 255;
    magic = 5; /* 8 bit images are always saved in binary mode */
  }


  if (image_info->colors == 1)
  {
    if (image_info->depth == 1)
    {
      /* do not touch the texts and length here, the reading routine needs to know the exact texts */
      fprintf(file, "P4\n"
                       "# XSane settings:\n"
                       "#  resolution_x    = %6.1f\n"
                       "#  resolution_y    = %6.1f\n"
                       "#  threshold       = %4.1f\n"
                       "# XSANE data follows\n"
                       "%05d %05d\n",
                       image_info->resolution_x,
                       image_info->resolution_y,
                       image_info->threshold,
                       image_info->image_width, image_info->image_height);
    }
    else if (image_info->reduce_to_lineart)
    {
      /* do not touch the texts and length here, the reading routine needs to know the exact texts */
      fprintf(file, "P%d\n"
                       "# XSane settings:\n"
                       "#  resolution_x    = %6.1f\n"
                       "#  resolution_y    = %6.1f\n"
                       "#  threshold       = %4.1f\n"
                       "#  reduce to lineart\n"
                       "# XSANE data follows\n"
                       "%05d %05d\n"
                       "%d\n",
                       magic, /* P5 for binary, P2 for ascii */
                       image_info->resolution_x,
                       image_info->resolution_y,
                       image_info->threshold,
                       image_info->image_width, image_info->image_height,
                       maxval);
    }
    else
    {
      fprintf(file, "P%d\n"
                       "# XSane settings:\n"
                       "#  resolution_x    = %6.1f\n"
                       "#  resolution_y    = %6.1f\n"
                       "#  gamma           = %3.2f\n"
                       "#  brightness      = %4.1f\n"
                       "#  contrast        = %4.1f\n"
                       "# XSANE data follows\n"
                       "%05d %05d\n"
                       "%d\n",
                       magic, /* P5 for binary, P2 for ascii */
                       image_info->resolution_x,
                       image_info->resolution_y,
                       image_info->gamma,
                       image_info->brightness,
                       image_info->contrast,
                       image_info->image_width, image_info->image_height,
                       maxval);
    }
  }
  else if (image_info->colors == 3)
  {
    fprintf(file, "P%d\n"
                     "# XSane settings:\n"
                     "#  resolution_x    = %6.1f\n"
                     "#  resolution_y    = %6.1f\n"
                     "#  gamma      IRGB = %3.2f %3.2f %3.2f %3.2f\n"
                     "#  brightness IRGB = %4.1f %4.1f %4.1f %4.1f\n"
                     "#  contrast   IRGB = %4.1f %4.1f %4.1f %4.1f\n"
                     "# XSANE data follows\n"
                     "%05d %05d\n" \
                     "%d\n",
                     magic+1, /* P6 for binary, P3 for ascii */
                     image_info->resolution_x,
                     image_info->resolution_y,
                     image_info->gamma,      image_info->gamma_red,      image_info->gamma_green,      image_info->gamma_blue,
                     image_info->brightness, image_info->brightness_red, image_info->brightness_green, image_info->brightness_blue,
                     image_info->contrast,   image_info->contrast_red,   image_info->contrast_green,   image_info->contrast_blue,
                     image_info->image_width, image_info->image_height,
                     maxval);
  }
#ifdef SUPPORT_RGBA
  else if (image_info->colors == 4)
  {
        fprintf(file, "SANE_RGBA\n" \
                         "%d %d\n" \
                         "%d\n",
                         image_info->image_width, image_info->image_height, maxval);
  }
#endif
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_copy_file(FILE *outfile, FILE *infile, GtkProgressBar *progress_bar, int *cancel_save)
{
 long size;
 long bytes_sum = 0;
 size_t bytes;
 unsigned char buf[65536];

  DBG(DBG_proc, "copying file\n");

  fseek(infile, 0, SEEK_END);
  size = ftell(infile);
  fseek(infile, 0, SEEK_SET);

  gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

  while (!feof(infile))
  {
    bytes = fread(buf, 1, sizeof(buf), infile);
    if (bytes > 0)
    {
      fwrite(buf, 1, bytes, outfile);
      bytes_sum += bytes;
    }

    gtk_progress_bar_update(progress_bar, (float) bytes_sum / size); /* update progress bar */

    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (ferror(infile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_READ, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

  fflush(outfile);

  if (size != bytes_sum)
  {
    DBG(DBG_info, "copy errro, not complete, %ld bytes of %ld bytes copied\n", bytes_sum, size);
    *cancel_save = 1;
   return (*cancel_save);
  }

  DBG(DBG_info, "copy complete, %ld bytes copied\n", bytes_sum);

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_copy_file_by_name(char *output_filename, char *input_filename, GtkProgressBar *progress_bar, int *cancel_save)
{
 FILE *infile;
 FILE *outfile;

  DBG(DBG_proc, "copying file %s to %s\n", input_filename, output_filename);

  outfile = fopen(output_filename, "wb"); /* b = binary mode for win32 */

  if (outfile == 0)
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, output_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);
   return -2;
  }

  infile = fopen(input_filename, "rb"); /* read binary (b for win32) */
  if (infile == 0)
  {
   char buf[TEXTBUFSIZE];
    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, input_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);     

    fclose(outfile);
    remove(output_filename); /* remove already created output file */
   return -1;
  }

  xsane_copy_file(outfile, infile, progress_bar, cancel_save);

  fclose(infile);
  fclose(outfile);

  gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), "");
  gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_grayscale_image_as_lineart(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x, y, bit;
 u_char bitval, packed;

  *cancel_save = 0;

  image_info->depth = 1;

  xsane_write_pnm_header(outfile, image_info, 0);

  for (y = 0; y < image_info->image_height; y++)
  {
    bit = 128;
    packed = 0;

    for (x = 0; x < image_info->image_width; x++)
    {
      bitval = fgetc(imagefile);

      if (!bitval) /* white gets 0 bit, black gets 1 bit */
      {
        packed |= bit;
      }

      if (bit == 1)
      {
        fputc(packed, outfile);
        bit = 128;
        packed = 0;
      }
      else
      {
        bit >>= 1;
      }
    }

    if (bit != 128)
    {
      fputc(packed, outfile);
      bit = 128;
      packed = 0;
    }

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }


    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height); /* update progress bar */
    while (gtk_events_pending()) /* give gtk the chance to display the changes */
    {
      gtk_main_iteration();
    }

    if (*cancel_save)
    {
      break;
    }
  }
 
 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_scaled_image(FILE *outfile, FILE *imagefile, Image_info *image_info, float x_scale, float y_scale, GtkProgressBar *progress_bar, int *cancel_save)
{
 int original_image_width  = image_info->image_width;
 int original_image_height = image_info->image_height;
 int new_image_width  = image_info->image_width  * x_scale + 0.5;
 int new_image_height = image_info->image_height * y_scale + 0.5;
 unsigned char *original_line;
 guint16 *original_line16 = NULL;
 unsigned char *new_line;
 float *pixel_val;
 float *pixel_norm;
 int bytespp = 1;
 float x, y;
 int c;
 int oldy;
 int x_new, y_new;
 float x_go, y_go;
 float factor, x_factor, y_factor;
 guint16 color;
 int read_line;

  DBG(DBG_proc, "xsane_save_scaled_image\n");

  *cancel_save = 0;

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  image_info->image_width  = new_image_width;
  image_info->image_height = new_image_height;
  image_info->resolution_x *= x_scale;
  image_info->resolution_y *= y_scale;

  original_line = malloc(original_image_width * image_info->colors * bytespp);
  if (!original_line)
  {
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }

  new_line = malloc(new_image_width * image_info->colors * bytespp);
  if (!new_line)
  {
    free(original_line);
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }

  pixel_val = malloc(new_image_width * image_info->colors * sizeof(float));
  if (!pixel_val)
  {
    free(original_line);
    free(new_line);
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }               

  pixel_norm = malloc(new_image_width * image_info->colors * sizeof(float));
  if (!pixel_norm)
  {
    free(original_line);
    free(new_line);
    free(pixel_val);
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }               

  xsane_write_pnm_header(outfile, image_info, 0);

  read_line = TRUE;

  memset(pixel_val,  0, new_image_width * image_info->colors * sizeof(float));
  memset(pixel_norm, 0, new_image_width * image_info->colors * sizeof(float));

  y_new = 0;
  y_go = 1.0 / y_scale;
  y_factor = 1.0;
  y = 0.0;

  while (y < original_image_height)
  {
    DBG(DBG_info2, "xsane_save_scaled_image: original line %d, new line %d\n", (int) y, y_new);

    gtk_progress_bar_update(progress_bar, (float) y / original_image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (read_line)
    {
      DBG(DBG_info, "xsane_save_scaled_image: reading original line %d\n", (int) y);
      fread(original_line, original_image_width, image_info->colors * bytespp, imagefile); /* read one line */
      original_line16 = (guint16 *) original_line;
    }

    x_new = 0;
    x_go = 1.0 / x_scale;
    x = 0.0;
    x_factor = 1.0;

    while ( (x < original_image_width) && (x_new < new_image_width) ) /* add this line to anti aliasing buffer */

    {
      factor = x_factor * y_factor;

      for (c = 0; c < image_info->colors; c++)
      {
        if (bytespp == 1)
        {
          color = original_line[((int) x) * image_info->colors + c];
        }
        else /* bytespp == 2 */
        {
          color = original_line16[((int) x) * image_info->colors + c];
        }

        pixel_val [x_new * image_info->colors + c] += factor * color;
        pixel_norm[x_new * image_info->colors + c] += factor;
      }

      x_go -= x_factor;

      if (x_go <= 0.0) /* change of pixel in new image */
      {
        x_new++;
        x_go = 1.0 / x_scale;

        x_factor = x - (int) x; /* use pixel rest */
        if (x_factor > x_go)
        {
          x_factor = x_go;
        }
      }
      else
      {
        x_factor = x_go;
      }

      if (x_factor > 1.0)
      {
        x_factor = 1.0;
      }

      x += x_factor;
    }

    y_go -= y_factor;

    if (y_go <= 0.0) /* normalize one line and write to destination image file */
    {
      DBG(DBG_info2, "xsane_save_scaled_image: writing new line %d\n", y_new);

      if (bytespp == 1)
      {
        for (x_new = 0; x_new < new_image_width * image_info->colors; x_new++)
        {
          new_line[x_new] = (int) (pixel_val[x_new] / pixel_norm[x_new]);
        }
      }
      else /* bytespp == 2 */
      {
       guint16 *new_line16 = (guint16 *) new_line;

        for (x_new = 0; x_new < new_image_width * image_info->colors; x_new++)
        {
          new_line16[x_new] = (int) (pixel_val[x_new] / pixel_norm[x_new]);
        }
      }

      fwrite(new_line, new_image_width, image_info->colors * bytespp, outfile); /* write one line */

      if (ferror(outfile))
      {
       char buf[TEXTBUFSIZE];
 
        snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
        DBG(DBG_error, "%s\n", buf);
        xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
        *cancel_save = 1;
       break;
      }

      /* reset values and norm factors */
      memset(pixel_val,  0, new_image_width * image_info->colors * sizeof(float));
      memset(pixel_norm, 0, new_image_width * image_info->colors * sizeof(float));

      y_new++;
      y_go = 1.0 / y_scale;

      y_factor = y - (int) y;
      if (y_factor > y_go)
      {
        y_factor = y_go;
      }
    }
    else
    {
      y_factor = y_go;
    }

    if (y_factor > 1.0)
    {
      y_factor = 1.0;
    }

    oldy = (int) y;
    y += y_factor;
    read_line = (oldy != (int) y);
  }

  free(original_line);
  free(new_line);
  free(pixel_val);
  free(pixel_norm);

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */
#if 0
int xsane_save_scaled_image(FILE *outfile, FILE *imagefile, Image_info *image_info, float x_scale, float y_scale, GtkProgressBar *progress_bar, int *cancel_save)
{
 float original_y;
 int old_original_y;
 int x, y, i;
 int original_image_width  = image_info->image_width;
 int new_image_width  = image_info->image_width * x_scale;
 int new_image_height = image_info->image_height * y_scale;
 unsigned char *original_line;
 unsigned char *new_line;
 int bytespp = 1;

  DBG(DBG_proc, "xsane_save_scaled_image\n");

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  image_info->image_width  = new_image_width;
  image_info->image_height = new_image_height;
  image_info->resolution_x *= x_scale;
  image_info->resolution_y *= y_scale;

  original_line = malloc(original_image_width * image_info->colors * bytespp);
  if (!original_line)
  {
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }

  new_line = malloc(new_image_width * image_info->colors * bytespp);
  if (!new_line)
  {
    free(original_line);
    DBG(DBG_error, "xsane_save_scaled_image: out of memory\n");
   return -1;
  }

  xsane_write_pnm_header(outfile, image_info, 0);

  original_y = 0.0;
  old_original_y = -1;

  for (y = 0; y < new_image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    for (; ((int) original_y) - old_original_y; old_original_y += 1)
    {
      fread(original_line, original_image_width, image_info->colors * bytespp, imagefile); /* read one line */
    }

    for (x = 0; x < new_image_width; x++)
    {
      for (i = 0; i < image_info->colors * bytespp; i++)
      {
        new_line[x * image_info->colors * bytespp + i] = original_line[((int) (x / x_scale)) * image_info->colors * bytespp + i];
      }
    }

    fwrite(new_line, new_image_width, image_info->colors * bytespp, outfile); /* write one line */

    original_y += 1/y_scale;

    if (*cancel_save)
    {
      break;
    }
  }

  free(original_line);
  free(new_line);

  fflush(outfile);

 return (*cancel_save);
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_despeckle_image(FILE *outfile, FILE *imagefile, Image_info *image_info, int radius, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x, y, sx, sy, i;
 int xmin, xmax;
 int ymin, ymax;
 int count;
 unsigned char *line_cache;
 unsigned char *line_cache_ptr;
 guint16 *color_cache;
 guint16 *color_cache_ptr;
 int bytespp = 1;
 int color_radius;
 int color_width  = image_info->image_width * image_info->colors;

  radius--; /* correct radius : 1 means nothing happens */

  if (radius < 1)
  {
    radius = 1;
  }

  color_radius = radius * image_info->colors;

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  xsane_write_pnm_header(outfile, image_info, 0);

  line_cache = malloc(color_width * bytespp * (2 * radius + 1));
  if (!line_cache)
  {
    DBG(DBG_error, "xsane_despeckle_image: out of memory\n");
   return -1;
  }

  fread(line_cache, color_width * bytespp, (2 * radius + 1), imagefile);

  color_cache = malloc((size_t) sizeof(guint16) * (2*radius+1)*(2*radius+1));

  if (!color_cache)
  {
    free(line_cache);
    DBG(DBG_error, "xsane_despeckle_image: out of memory\n");
   return -1;
  }

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float)  y / image_info->image_height);

    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    ymin = y - radius;
    ymax = y + radius;

    if (ymin < 0)
    {
      ymin = 0;
    }

    if (ymax > image_info->image_height)
    {
      ymax = image_info->image_height;
    }

    for (x = 0; x < color_width; x++)
    {
      xmin = x - color_radius;
      xmax = x + color_radius;

      if (xmin < 0)
      {
        xmin = x % image_info->colors;
      }

      if (xmax > color_width)
      {
        xmax = color_width;
      }

      count = 0;

      color_cache_ptr = color_cache;


      if (bytespp == 1)
      {
        for (sy = ymin; sy <= ymax; sy++) /* search area defined by radius  - y part */
        {
          line_cache_ptr = line_cache + (sy-ymin) * color_width + xmin;

          for (sx = xmin; sx <= xmax; sx+=image_info->colors) /* x part */
          {
            *color_cache_ptr = *line_cache_ptr;
            color_cache_ptr++;
            line_cache_ptr += image_info->colors;
          }
        }

        /* sort color_cache */

        count = color_cache_ptr - color_cache;

        if (count > 1)
        {
         int d, j, val;

          for (d = count / 2; d > 0; d = d / 2)
          {
            for (i = d; i < count; i++)
            {
              for (j = i - d, color_cache_ptr = color_cache + j; j >= 0 && color_cache_ptr[0] > color_cache_ptr[d]; j -= d, color_cache_ptr -= d)
              {
                val                = color_cache_ptr[0];
                color_cache_ptr[0] = color_cache_ptr[d];
                color_cache_ptr[d] = val;
              };
            }
          }
        }

        fputc((char) (color_cache[count/2]), outfile);
      }
      else /* 16 bit/color */
      {
       guint16 val16;
       guint16 *line_cache16 = (guint16 *) line_cache;
       guint16 *line_cache16_ptr;
       char *bytes16 = (char *) &val16;

        for (sy = ymin; sy <= ymax; sy++)
        {
          line_cache16_ptr = line_cache16 + (sy-ymin) * color_width + xmin;

          for (sx = xmin; sx <= xmax; sx+=image_info->colors)
          {
            *color_cache_ptr = *line_cache16_ptr;
            color_cache_ptr++;
            line_cache16_ptr += image_info->colors;
          }
        }

        /* sort color_cache */

        count = color_cache_ptr - color_cache;

        if (count > 1)
        {
         int d,j, val;

          for (d = count / 2; d > 0; d = d / 2)
          {
            for (i = d; i < count; i++)
            {
              for (j = i - d, color_cache_ptr = color_cache + j; j >= 0 && color_cache_ptr[0] > color_cache_ptr[d]; j -= d, color_cache_ptr -= d)
              {
                val                = color_cache_ptr[0];
                color_cache_ptr[0] = color_cache_ptr[d];
                color_cache_ptr[d] = val;
              };
            }
          }
        }

        val16 = color_cache[count/2];
        fputc(bytes16[0], outfile); /* write bytes in machine byte order */
        fputc(bytes16[1], outfile);
      }
    }

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if ((y > radius) && (y < image_info->image_height - radius))
    {
      memcpy(line_cache, line_cache + color_width * bytespp, 
             color_width * bytespp * 2 * radius);
      fread(line_cache + color_width * bytespp * 2 * radius,
            color_width * bytespp, 1, imagefile);
    }
  }

  fflush(outfile);

  free(line_cache);
  free(color_cache);

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_blur_image(FILE *outfile, FILE *imagefile, Image_info *image_info, float radius, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x, y, sx, sy;
 int xmin, xmax;
 int ymin, ymax;
 double val, norm, outer_factor;
 unsigned char *line_cache;
 int bytespp = 1;
 int intradius;
 int xmin_flag;
 int xmax_flag;
 int ymin_flag;
 int ymax_flag;

  *cancel_save = 0;

  intradius = (int) radius;

  outer_factor = radius - (int) radius;

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  xsane_write_pnm_header(outfile, image_info, 0);

  line_cache = malloc(image_info->image_width * image_info->colors * bytespp * (2 * intradius + 1));
  if (!line_cache)
  {
    DBG(DBG_error, "xsane_blur_image: out of memory\n");
   return -1;
  }

  fread(line_cache, image_info->image_width * image_info->colors * bytespp, (2 * intradius + 1), imagefile);

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float)  y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    for (x = 0; x < image_info->image_width * image_info->colors; x++)
    {
      xmin_flag = xmax_flag = ymin_flag = ymax_flag = TRUE;

      xmin = x - intradius * image_info->colors;
      xmax = x + intradius * image_info->colors;

      if (xmin < 0)
      {
        xmin = x % image_info->colors;
        xmin_flag = FALSE;
      }

      if (xmax > image_info->image_width * image_info->colors)
      {
        xmax = image_info->image_width * image_info->colors;
        xmax_flag = FALSE;
      }

      ymin = y - intradius;
      ymax = y + intradius;

      if (ymin < 0)
      {
        ymin = 0;
        ymin_flag = FALSE;
      }

      if (ymax > image_info->image_height)
      {
        ymax = image_info->image_height;
        ymax_flag = FALSE;
      }

      val  = 0.0;
      norm = 0.0;

      if (bytespp == 1)
      {
        if (xmin_flag) /* integrate over left margin */
        {
          for (sy = ymin+1; sy <= ymax-1 ; sy++)
          {
            val += outer_factor * line_cache[(sy-ymin) * image_info->image_width * image_info->colors + xmin];
            norm += outer_factor;
          }
        }

        if (xmax_flag) /* integrate over right margin */
        {
          for (sy = ymin+1; sy <= ymax-1 ; sy++)
          {
            val += outer_factor * line_cache[(sy-ymin) * image_info->image_width * image_info->colors + xmax];
            norm += outer_factor;
          }
        }

        if (ymin_flag) /* integrate over top margin */
        {
          for (sx = xmin+image_info->colors; sx <= xmax-image_info->colors ; sx += image_info->colors)
          {
            val += outer_factor * line_cache[sx];
            norm += outer_factor;
          }
        }

        if (ymax_flag) /* integrate over bottom margin */
        {
          for (sx = xmin+image_info->colors; sx <= xmax-image_info->colors ; sx += image_info->colors)
          {
            val += outer_factor * line_cache[(ymax-ymin) * image_info->image_width * image_info->colors + sx];
            norm += outer_factor;
          }
        }

        for (sy = ymin+1; sy <= ymax-1; sy++) /* integrate internal square */
        {
          for (sx = xmin+image_info->colors; sx <= xmax-image_info->colors; sx+=image_info->colors)
          {
            val += line_cache[(sy-ymin) * image_info->image_width * image_info->colors + sx];
            norm += 1.0;
          }
        }
        fputc((char) ((int) (val/norm)), outfile);
      }
      else /* bytespp == 2 */
      {
       guint16 *line_cache16 = (guint16 *) line_cache;
       guint16 val16;
       char *bytes16 = (char *) &val16;

        if (xmin_flag) /* integrate over left margin */
        {
          for (sy = ymin+1; sy <= ymax-1 ; sy++)
          {
            val += outer_factor * line_cache16[(sy-ymin) * image_info->image_width * image_info->colors + xmin];
            norm += outer_factor;
          }
        }

        if (xmax_flag) /* integrate over right margin */
        {
          for (sy = ymin+1; sy <= ymax-1 ; sy++)
          {
            val += outer_factor * line_cache16[(sy-ymin) * image_info->image_width * image_info->colors + xmax];
            norm += outer_factor;
          }
        }

        if (ymin_flag) /* integrate over top margin */
        {
          for (sx = xmin+image_info->colors; sx <= xmax-image_info->colors ; sx += image_info->colors)
          {
            val += outer_factor * line_cache16[sx];
            norm += outer_factor;
          }
        }

        if (ymax_flag) /* integrate over bottom margin */
        {
          for (sx = xmin+image_info->colors; sx <= xmax-image_info->colors ; sx += image_info->colors)
          {
            val += outer_factor * line_cache16[(ymax-ymin) * image_info->image_width * image_info->colors + sx];
            norm += outer_factor;
          }
        }

        for (sy = ymin; sy <= ymax; sy++) /* integrate internal square */
        {
          for (sx = xmin; sx <= xmax; sx+=image_info->colors)
          {
            val += line_cache16[(sy-ymin) * image_info->image_width * image_info->colors + sx];
            norm += 1.0;
          }
        }

        val16 = val / norm;
        fputc(bytes16[0], outfile); /* write bytes in machine byte order */
        fputc(bytes16[1], outfile);
      }
    }

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

      /* reset values and norm factors */

    if ((y > intradius) && (y < image_info->image_height - intradius))
    {
      memcpy(line_cache, line_cache + image_info->image_width * image_info->colors * bytespp, 
             image_info->image_width * image_info->colors * bytespp * 2 * intradius);
      fread(line_cache + image_info->image_width * image_info->colors * bytespp * 2 * intradius,
            image_info->image_width * image_info->colors * bytespp, 1, imagefile);
    }
  }

  fflush(outfile);
  free(line_cache);

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

#if 0
int xsane_save_blur_image(FILE *outfile, FILE *imagefile, Image_info *image_info, int radius, GtkProgressBar *progress_bar)
{
 int x, y, sx, sy;
 int xmin, xmax;
 int ymin, ymax;
 int pos0;
 int val, count;
 unsigned char *line_cache;
 int bytespp = 1;

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  pos0 = ftell(imagefile); /* mark position to skip header */

  xsane_write_pnm_header(outfile, image_info, 0);

  line_cache = malloc(image_info->image_width * image_info->colors * bytespp * (2 * radius + 1));
  if (!line_cache)
  {
    DBG(DBG_error, "xsane_blur_image: out of memory\n");
   return -1;
  }

  fread(line_cache, image_info->image_width * image_info->colors * bytespp, (2 * radius + 1), imagefile);

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float)  y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    for (x = 0; x < image_info->image_width * image_info->colors; x++)
    {
      xmin = x - radius * image_info->colors;
      xmax = x + radius * image_info->colors;

      if (xmin < 0)
      {
        xmin = x % image_info->colors;
      }

      if (xmax > image_info->image_width * image_info->colors)
      {
        xmax = image_info->image_width * image_info->colors;
      }

      ymin = y - radius;
      ymax = y + radius;

      if (ymin < 0)
      {
        ymin = 0;
      }

      if (ymax > image_info->image_height)
      {
        ymax = image_info->image_height;
      }

      val = 0;
      count = 0;

      if (bytespp == 1)
      {
        for (sy = ymin; sy <= ymax; sy++)
        {
          for (sx = xmin; sx <= xmax; sx+=image_info->colors)
          {
            val += line_cache[(sy-ymin) * image_info->image_width * image_info->colors + sx];
            count++;
          }
        }
        fputc((char) (val/count), outfile);
      }
      else
      {
       guint16 *line_cache16 = (guint16 *) line_cache;
       guint16 val16;
       char *bytes16 = (char *) &val16;

        for (sy = ymin; sy <= ymax; sy++)
        {
          for (sx = xmin; sx <= xmax; sx+=image_info->colors)
          {
            val += line_cache16[(sy-ymin) * image_info->image_width * image_info->colors + sx];
            count++;
          }
        }

        val16 = val / count;
        fputc(bytes16[0], outfile); /* write bytes in machine byte order */
        fputc(bytes16[1], outfile);
      }
    }

    if ((y > radius) && (y < image_info->image_height - radius))
    {
      memcpy(line_cache, line_cache + image_info->image_width * image_info->colors * bytespp, 
             image_info->image_width * image_info->colors * bytespp * 2 * radius);
      fread(line_cache + image_info->image_width * image_info->colors * bytespp * 2 * radius,
            image_info->image_width * image_info->colors * bytespp, 1, imagefile);
    }
  }

  fflush(outfile);
  free(line_cache);

 return 0;
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_rotate_image(FILE *outfile, FILE *imagefile, Image_info *image_info, int rotation, GtkProgressBar *progress_bar, int *cancel_save)
/* returns true if operation was cancelled */
{
 int x, y, pos0, bytespp, i;
 int pixel_width  = image_info->image_width;
 int pixel_height = image_info->image_height;
 float resolution_x = image_info->resolution_x;
 float resolution_y = image_info->resolution_y;

#ifdef HAVE_MMAP
 char *mmaped_imagefile = NULL;
#endif

  DBG(DBG_proc, "xsane_save_rotate_image\n");

  *cancel_save = 0;

  pos0 = ftell(imagefile); /* mark position to skip header */

  bytespp = image_info->colors;

  if (image_info->depth > 8)
  {
    bytespp *= 2;
  }

  if (image_info->depth < 8) /* lineart images are expanded to grayscale until transformation is done */
  {
    image_info->depth = 8; /* so we have at least 8 bits/pixel here */
  }

#ifdef HAVE_MMAP
  mmaped_imagefile = mmap(NULL, pixel_width * pixel_height * bytespp + pos0, PROT_READ, MAP_PRIVATE, fileno(imagefile), 0);
  if (mmaped_imagefile == (char *) -1) /* mmap failed */
  {
    DBG(DBG_info, "xsane_save_rotate_image: unable to memory map image file, using standard file access\n");
    mmaped_imagefile = NULL;
  }
  else
  {
    DBG(DBG_info, "xsane_save_rotate_image: using memory mapped image file\n");
  }
#endif

  switch (rotation)
  {
    default:
     break;

    case 0: /* 0 degree */
      xsane_write_pnm_header(outfile, image_info, 0);

      for (y = 0; y < pixel_height; y++)
      {
        gtk_progress_bar_update(progress_bar, (float) y / pixel_height);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (x = 0; x < pixel_width; x++)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i=0; i<bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }

        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;

    case 1: /* 90 degree */
      image_info->image_width  = pixel_height;
      image_info->image_height = pixel_width;

      image_info->resolution_x = resolution_y;
      image_info->resolution_y = resolution_x;

      xsane_write_pnm_header(outfile, image_info, 0);

      for (x=0; x<pixel_width; x++)
      {
        gtk_progress_bar_update(progress_bar, (float) x / pixel_width);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (y=pixel_height-1; y>=0; y--)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width); /* calculate correct position */

            for (i=0; i<bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i=0; i<bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }

     break;

    case 2: /* 180 degree */
      xsane_write_pnm_header(outfile, image_info, 0);

      for (y = pixel_height-1; y >= 0; y--)
      {
        gtk_progress_bar_update(progress_bar, (float) (pixel_height - y) / pixel_height);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (x = pixel_width-1; x >= 0; x--)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i = 0; i < bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;

    case 3: /* 270 degree */
      image_info->image_width  = pixel_height;
      image_info->image_height = pixel_width;

      image_info->resolution_x = resolution_y;
      image_info->resolution_y = resolution_x;

      xsane_write_pnm_header(outfile, image_info, 0);

      for (x = pixel_width-1; x >= 0; x--)
      {
        gtk_progress_bar_update(progress_bar, (float) (pixel_width - x) / pixel_width);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (y = 0; y < pixel_height; y++)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i = 0; i < bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;

    case 4: /* 0 degree, x mirror */
      xsane_write_pnm_header(outfile, image_info, 0);

      for (y = 0; y < pixel_height; y++)
      {
        gtk_progress_bar_update(progress_bar, (float) y / pixel_height);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (x = pixel_width-1; x >= 0; x--)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i = 0; i < bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;

    case 5: /* 90 degree, x mirror */
      image_info->image_width  = pixel_height;
      image_info->image_height = pixel_width;

      image_info->resolution_x = resolution_y;
      image_info->resolution_y = resolution_x;

      xsane_write_pnm_header(outfile, image_info, 0);

      for (x = 0; x < pixel_width; x++)
      {
        gtk_progress_bar_update(progress_bar, (float) x / pixel_width);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (y = 0; y < pixel_height; y++)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width); /* calculate correct position */

            for (i=0; i<bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }

     break;

    case 6: /* 180 degree, x mirror */
      xsane_write_pnm_header(outfile, image_info, 0);

      for (y = pixel_height-1; y >= 0; y--)
      {
        gtk_progress_bar_update(progress_bar, (float) (pixel_height - y) / pixel_height);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (x = 0; x < pixel_width; x++)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i = 0; i < bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;

    case 7: /* 270 degree, x mirror */
      image_info->image_width  = pixel_height;
      image_info->image_height = pixel_width;

      image_info->resolution_x = resolution_y;
      image_info->resolution_y = resolution_x;

      xsane_write_pnm_header(outfile, image_info, 0);

      for (x = pixel_width-1; x >= 0; x--)
      {
        gtk_progress_bar_update(progress_bar, (float) (pixel_width - x) / pixel_width);
        while (gtk_events_pending())
        {
          gtk_main_iteration();
        }

        for (y = pixel_height-1; y >= 0; y--)
        {
#ifdef HAVE_MMAP
          if (mmaped_imagefile)
          {
           char *p = mmaped_imagefile + pos0 + bytespp * (x + y * pixel_width);  /* calculate correct position */

            for (i = 0; i < bytespp; i++)
            {
              fputc(*p++, outfile);
            }
          }
          else
#endif
          {
            fseek(imagefile, pos0 + bytespp * (x + y * pixel_width), SEEK_SET); /* go to the correct position */
            for (i = 0; i < bytespp; i++)
            {
              fputc(fgetc(imagefile), outfile);
            }
          }
        }


        if (ferror(outfile))
        {
         char buf[TEXTBUFSIZE];

          snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
          DBG(DBG_error, "%s\n", buf);
          xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
          *cancel_save = 1;
         break;
        }

        if (*cancel_save)
        {
          break;
        }
      }
     break;
  }

#ifdef HAVE_MMAP
  if (mmaped_imagefile)
  {
    munmap(mmaped_imagefile, pos0 + pixel_width * pixel_height * bytespp);
  }
#endif

  fflush(outfile);

  return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_save_ps_create_document_header(FILE *outfile, int pages, int flatdecode)
{
  DBG(DBG_proc, "xsane_save_ps_create_document_header\n");

  fprintf(outfile, "%%!PS-Adobe-3.0\n");
  fprintf(outfile, "%%%%Creator: XSane version %s (sane %d.%d) - by Oliver Rauch\n", VERSION,
                        SANE_VERSION_MAJOR(xsane.sane_backend_versioncode),
                        SANE_VERSION_MINOR(xsane.sane_backend_versioncode));
  fprintf(outfile, "%%%%DocumentData: Clean7Bit\n");
  if (flatdecode)
  {
    fprintf(outfile, "%%%%LanguageLevel: 3\n");
  }
  else
  {
    fprintf(outfile, "%%%%LanguageLevel: 2\n");
  }

  if (pages)
  {
    fprintf(outfile, "%%%%Pages: %d\n", pages);
  }
  else
  {
    fprintf(outfile, "%%%%Pages: (atend)\n");
  }
  
  fprintf(outfile, "%%%%EndComments\n");
  fprintf(outfile, "\n");
  fprintf(outfile, "/origstate save def\n");
  fprintf(outfile, "20 dict begin\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_save_ps_create_document_trailer(FILE *outfile, int pages)
{
  DBG(DBG_proc, "xsane_save_ps_create_document_trailer\n");

  fprintf(outfile, "end\n");
  fprintf(outfile, "origstate restore\n");

  if (pages)
  {
    fprintf(outfile, "%%%%Trailer\n");
    fprintf(outfile, "%%%%Pages: %d\n", pages);
  }

  fprintf(outfile, "%%%%EOF\n");
  fprintf(outfile, "\n");

}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* page = [1 .. pages] */
static void xsane_save_ps_create_page_header(FILE *outfile, int page,
                                             Image_info *image_info,
                                             float width, float height,
                                             int paper_left_margin, int paper_bottom_margin,
                                             int paper_width, int paper_height,
                                             int paper_orientation, int flatdecode,
                                             GtkProgressBar *progress_bar)
{
 int degree, position_left, position_bottom, box_left, box_bottom, box_right, box_top, depth;
 int left, bottom;

  DBG(DBG_proc, "xsane_save_ps_create_page_header\n");

  switch (paper_orientation)
  {
    default:
    case 0: /* top left portrait */
      left   = 0.0;
      bottom = paper_height - height;
     break;

    case 1: /* top right portrait */
      left   = paper_width  - width;
      bottom = paper_height - height;
     break;

    case 2: /* bottom right portrait */
      left   = paper_width - width;
      bottom = 0.0;
     break;

    case 3: /* bottom left portrait */
      left   = 0.0;
      bottom = 0.0;
     break;

    case 4: /* center portrait */
      left   = paper_width  / 2.0 - width  / 2.0;
      bottom = paper_height / 2.0 - height / 2.0;
     break;


    case 8: /* top left landscape */
      left   = 0.0;
      bottom = paper_width - height;
     break;

    case 9: /* top right landscape */
      left   = paper_height - width;
      bottom = paper_width  - height;
     break;

    case 10: /* bottom right landscape */
      left   = paper_height - width;
      bottom = 0.0;
     break;

    case 11: /* bottom left landscape */
      left   = 0.0;
      bottom = 0.0;
     break;

    case 12: /* center landscape */
      left   = paper_height / 2.0 - width  / 2.0;
      bottom = paper_width  / 2.0 - height / 2.0;
     break;
  }


  if (paper_orientation >= 8) /* rotate with 90 degrees - landscape mode */
  {
    degree = 90;
    position_left   = left   + paper_bottom_margin;
    position_bottom = bottom - paper_width - paper_left_margin;
    box_left        = paper_width - paper_left_margin - bottom - height;
    box_bottom      = left   + paper_bottom_margin;
    box_right       = box_left   + ceil(height);
    box_top         = box_bottom + ceil(width);
  }
  else /* do not rotate, portrait mode */
  {
    degree = 0;
    position_left   = left   + paper_left_margin;
    position_bottom = bottom + paper_bottom_margin;
    box_left        = left   + paper_left_margin;
    box_bottom      = bottom + paper_bottom_margin;
    box_right       = box_left   + ceil(width);
    box_top         = box_bottom + ceil(height);
  }

  depth = image_info->depth;

  if (depth > 8)
  {
    depth = 8;
  }

  fprintf(outfile, "\n");
  fprintf(outfile, "%%%%Page: %d %d\n", page, page);
  fprintf(outfile, "%%%%PageBoundingBox: %d %d %d %d\n", box_left, box_bottom, box_right, box_top);

  if (depth == 1)
  {
    fprintf(outfile, "/grays %d string def\n", image_info->image_width);
    fprintf(outfile, "/npixels 0 def\n");
    fprintf(outfile, "/rgbindx 0 def\n");
  }

  fprintf(outfile, "%d rotate\n", degree);
  fprintf(outfile, "%d %d translate\n", position_left, position_bottom);
  fprintf(outfile, "%f %f scale\n", width, height);
  fprintf(outfile, "%d %d %d\n", image_info->image_width, image_info->image_height, depth);
  fprintf(outfile, "[%d %d %d %d %d %d]\n", image_info->image_width, 0, 0, -image_info->image_height, 0, image_info->image_height);
  fprintf(outfile, "currentfile\n");
  fprintf(outfile, "/ASCII85Decode filter\n");
#ifdef HAVE_LIBZ
  if (flatdecode)
  {
    fprintf(outfile, "/FlateDecode filter\n");
  }
#endif

  if (image_info->colors == 3) /* what about RGBA here ? */
  {
    fprintf(outfile, "false 3 colorimage\n");
    fprintf(outfile, "\n");
  }
  else
  {
    fprintf(outfile, "image\n");
    fprintf(outfile, "\n");
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_save_ps_create_page_trailer(FILE *outfile)
{
  fprintf(outfile, "\n");
  fprintf(outfile, "showpage\n");
  fprintf(outfile, "%%%%PageTrailer\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_LIBZ
/* Utility function for the PostScript output */
static int xsane_write_compressed_a85_flatdecode(FILE *outfile, unsigned char *line, int len, int finish)
{
  static unsigned char *cbuf = NULL;
  static int cbuflen = 0;
  static int linelen = 0;
  int i, j;
  int outlen;
  static int init = 0;
  static z_stream s;
  int ret;
  int flush;
  static int a85count = 0;
  static guint32 a85tuple = 0;
  static unsigned char a85block[6] = {0, 0, 0, 0, 0, 0};
  static int count = 0;

  DBG(DBG_proc, "xsane_write_compressed_a85_flatdecode\n");

  if (linelen != len)
  {
    linelen = len;
    if (cbuf != NULL)
    {
      free(cbuf);
    }
    /* buffer length = length + 0.1 * length + 12 (mandatory) */
    cbuflen = len + len / 10 + 12;
    cbuf = malloc(cbuflen);
  }

  if (cbuf == NULL)
  {
    DBG(DBG_error, "cbuf allocation failed\n");
   return 1;
  }

  if (!init)
  {
    s.zalloc = Z_NULL;
    s.zfree = Z_NULL;
    s.opaque = Z_NULL;

    ret = deflateInit(&s, Z_DEFAULT_COMPRESSION);

    if (ret != Z_OK)
    {
      DBG(DBG_error, "deflateInit failed\n");
      free(cbuf);
     return 1;
    }

    init = 1;
  }
  
  s.avail_in = len;
  s.next_in = line;

  do
  {
    s.avail_out = cbuflen;
    s.next_out = cbuf;

    flush = (finish) ? Z_FINISH : Z_NO_FLUSH;

    ret = deflate(&s, flush);

    if (ret == Z_STREAM_ERROR)
    {
      DBG(DBG_error, "deflate failed\n");
      free(cbuf);
     return 1;
    }

    outlen = cbuflen - s.avail_out;

    /* ASCII85 (base 85) encoding */
    for (i = 0; i < outlen; i++)
    {
      switch (a85count)
      {
        case 0:
          a85tuple |= (cbuf[i] << 24);
          a85count++;
         break;

        case 1:
          a85tuple |= (cbuf[i] << 16);
          a85count++;
         break;

        case 2:
          a85tuple |= (cbuf[i] << 8);
          a85count++;
         break;

        case 3:
          a85tuple |= (cbuf[i] << 0);

          if (count == 40)
          {
            fprintf(outfile, "\n");
            count = 0;
          }

          if (a85tuple == 0)
          {
            fprintf(outfile, "z");
            count++;
          }
          else
          {
            /* The ASCII chars must be written in reverse order, hence -> a85block[4-j] */
            for (j = 0; j < 5; j++)
            {
              a85block[4-j] = a85tuple % 85 + '!';
              a85tuple /= 85;
            }

            for (j = 0; j < 5; j++)
            {
              fprintf(outfile, "%c", a85block[j]);
              count++;
              if (count == 40)
              {
                fprintf(outfile, "\n");
                count = 0;
              }
            }
          }

          a85count = 0;
          a85tuple = 0;
         break;

        default:
         break;
      }
    }
  } while (s.avail_out == 0);

  if (finish)
  {
    DBG(DBG_info, "finish\n");
    if (a85count > 0)
    {
      a85count++;
      for (j = 0; j <= a85count; j++)
      {
        a85block[j] = a85tuple % 85 + '!';
        a85tuple /= 85;
      }
      /* Reverse order */
      for (j--; j > 0; j--)
      {
        if (count == 40)
        {
          fprintf(outfile, "\n");
          count = 0;
        }
        fprintf(outfile, "%c", a85block[j]);
        count++;
      }
    }

    /* ASCII85 EOD marker + newline*/
    if (count + 2 > 40)
    {
      fprintf(outfile, "\n");
    }
    fprintf(outfile, "~>\n");
    deflateEnd(&s);
    free(cbuf);
    cbuf = NULL;
    init = 0;
    a85tuple = 0;
    a85count = 0;
    cbuflen = 0;
    linelen = 0;
    count = 0;
  }

 return 0;
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

/* Utility function for the PostScript output */
static int xsane_write_compressed_a85(FILE *outfile, unsigned char *line, int len, int finish)
{
  static unsigned char *cbuf = NULL;
  static int cbuflen = 0;
  static int linelen = 0;
  int i, j;
  int outlen;
  static int a85count = 0;
  static guint32 a85tuple = 0;
  static unsigned char a85block[6] = {0, 0, 0, 0, 0, 0};
  static int count = 0;

  DBG(DBG_proc, "xsane_write_compressed_a85\n");

    cbuf = line;
    outlen = len;

    /* ASCII85 (base 85) encoding */
    for (i = 0; i < outlen; i++)
    {
      switch (a85count)
      {
        case 0:
          a85tuple |= (cbuf[i] << 24);
          a85count++;
         break;

        case 1:
          a85tuple |= (cbuf[i] << 16);
          a85count++;
         break;

        case 2:
          a85tuple |= (cbuf[i] << 8);
          a85count++;
         break;

        case 3:
          a85tuple |= (cbuf[i] << 0);

          if (count == 40)
          {
            fprintf(outfile, "\n");
            count = 0;
          }

          if (a85tuple == 0)
          {
            fprintf(outfile, "z");
            count++;
          }
          else
          {
            /* The ASCII chars must be written in reverse order, hence -> a85block[4-j] */
            for (j = 0; j < 5; j++)
            {
              a85block[4-j] = a85tuple % 85 + '!';
              a85tuple /= 85;
            }

            for (j = 0; j < 5; j++)
            {
              fprintf(outfile, "%c", a85block[j]);
              count++;
              if (count == 40)
              {
                fprintf(outfile, "\n");
                count = 0;
              }
            }
          }

          a85count = 0;
          a85tuple = 0;
         break;

        default:
         break;
      }
    }

  if (finish)
  {
    DBG(DBG_info, "finish\n");
    if (a85count > 0)
    {
      a85count++;
      for (j = 0; j <= a85count; j++)
      {
        a85block[j] = a85tuple % 85 + '!';
        a85tuple /= 85;
      }
      /* Reverse order */
      for (j--; j > 0; j--)
      {
        if (count == 40)
        {
          fprintf(outfile, "\n");
          count = 0;
        }
        fprintf(outfile, "%c", a85block[j]);
        count++;
      }
    }

    /* ASCII85 EOD marker + newline*/
    if (count + 2 > 40)
    {
      fprintf(outfile, "\n");
    }
    fprintf(outfile, "~>\n");
    a85tuple = 0;
    a85count = 0;
    cbuflen = 0;
    linelen = 0;
    count = 0;
  }

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_ps_pdf_bw(FILE *outfile, FILE *imagefile, Image_info *image_info, int flatdecode, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x, y;
 int bytes_per_line = (image_info->image_width+7)/8;
 int ret;
 unsigned char *line;

  DBG(DBG_proc, "xsane_save_ps_pdf_bw\n");

  *cancel_save = 0;

  line = (unsigned char *) malloc(bytes_per_line);

  if (line == NULL)
  {
    char buf[TEXTBUFSIZE];
      
    snprintf(buf, sizeof(buf), "%s malloc failed", ERR_DURING_SAVE);
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
   return (*cancel_save);
  }

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    for (x = 0; x < bytes_per_line; x++)
    {
      line[x] = fgetc(imagefile) ^ 255;
    }

#ifdef HAVE_LIBZ
    if (flatdecode)
    {
      ret = xsane_write_compressed_a85_flatdecode(outfile, line, bytes_per_line, (y == image_info->image_height - 1));
    }
    else
#endif
    {
      ret = xsane_write_compressed_a85(outfile, line, bytes_per_line, (y == image_info->image_height - 1));
    }

    if ((ret != 0) || (ferror(outfile)))
    {
     char buf[TEXTBUFSIZE];

      if (ret == 0)
      {
        snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      }
      else
      {
        snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_ZLIB);
      }

      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;

     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

  free(line);

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_ps_pdf_gray(FILE *outfile, FILE *imagefile, Image_info *image_info, int flatdecode, GtkProgressBar *progress_bar, int *cancel_save)
{
  int x, y;
  int ret;
  unsigned char *line;

  DBG(DBG_proc, "xsane_save_ps_pdf_gray\n");

  *cancel_save = 0;

  line = (unsigned char *) malloc(image_info->image_width);

  if (line == NULL)
  {
   char buf[TEXTBUFSIZE];
      
    snprintf(buf, sizeof(buf), "%s malloc failed", ERR_DURING_SAVE);
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
   return (*cancel_save);
  }

  for (y = 0; y < image_info->image_height; y++)
  {
    if (image_info->depth > 8) /* reduce 16 bit images */
    {
     guint16 val;

      for (x = 0; x < image_info->image_width; x++)
      {
        fread(&val, 2, 1, imagefile);
        line[x] = val/256;
      }
    }
    else /* 8 bits/sample */
    {
      for (x = 0; x < image_info->image_width; x++)
      {
        line[x] = fgetc(imagefile);
      }
    }

#ifdef HAVE_LIBZ
    if (flatdecode)
    {
      ret = xsane_write_compressed_a85_flatdecode(outfile, line, image_info->image_width, (y == image_info->image_height - 1));
    }
    else
#endif
    {
      ret = xsane_write_compressed_a85(outfile, line, image_info->image_width, (y == image_info->image_height - 1));
    }

    if ((ret != 0) || (ferror(outfile)))
    {
     char buf[TEXTBUFSIZE];

      if (ret == 0)
      {
        snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      }
      else
      {
        snprintf(buf, sizeof(buf), "%s zlib error or memory allocation problem", ERR_DURING_SAVE);
      }

      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;

     break;
    }

    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (*cancel_save)
    {
      break;
    }
  }

  free(line);

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_ps_pdf_color(FILE *outfile, FILE *imagefile, Image_info *image_info, int flatdecode, GtkProgressBar *progress_bar, int *cancel_save)
{
  int x, y;
  int ret;
  unsigned char *line, *linep;

  DBG(DBG_proc, "xsane_save_ps_pdf_color\n");

  *cancel_save = 0;

  line = (unsigned char *) malloc(image_info->image_width * 3);

  if (line == NULL)
  {
   char buf[TEXTBUFSIZE];
      
    snprintf(buf, sizeof(buf), "%s malloc failed", ERR_DURING_SAVE);
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
   return (*cancel_save);
  }
 
  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    linep = line;

    if (image_info->depth > 8) /* reduce 16 bit images */
    {
     guint16 val;

      for (x = 0; x < image_info->image_width; x++)
      {
        fread(&val, 2, 1, imagefile);
	*linep++ = val/256;
        fread(&val, 2, 1, imagefile);
	*linep++ = val/256;
        fread(&val, 2, 1, imagefile);
	*linep++ = val/256;
      }
    }
    else /* 8 bits/sample */
    {
      for (x = 0; x < image_info->image_width; x++)
      {
	*linep++ = fgetc(imagefile);
	*linep++ = fgetc(imagefile);
	*linep++ = fgetc(imagefile);
      }
    }

#ifdef HAVE_LIBZ
    if (flatdecode)
    {
      ret = xsane_write_compressed_a85_flatdecode(outfile, line, (image_info->image_width * 3), (y == image_info->image_height - 1));
    }
    else
#endif
    {
      ret = xsane_write_compressed_a85(outfile, line, (image_info->image_width * 3), (y == image_info->image_height - 1));
    }

    if ((ret != 0) || (ferror(outfile)))
    {
     char buf[TEXTBUFSIZE];

      if (ret == 0)
      {
        snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      }
      else
      {
        snprintf(buf, sizeof(buf), "%s zlib error or memory allocation problem", ERR_DURING_SAVE);
      }

      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;

     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

  free(line);

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_ps_page(FILE *outfile, int page,
                       FILE *imagefile, Image_info *image_info, float width, float height,
                       int paper_left_margin, int paper_bottom_margin, int paperwidth, int paperheight, int paper_orientation,
                       int flatdecode,
                       GtkProgressBar *progress_bar, int *cancel_save)
{
  DBG(DBG_proc, "xsane_save_ps_page\n");

  xsane_save_ps_create_page_header(outfile, page,
                                   image_info, width, height,
                                   paper_left_margin, paper_bottom_margin, paperwidth, paperheight, paper_orientation,
                                   flatdecode,
                                   progress_bar);

  if (image_info->colors == 1) /* lineart, halftone, grayscale */
  {
    if (image_info->depth == 1) /* lineart, halftone */
    {
      xsane_save_ps_pdf_bw(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
    }
    else /* grayscale */
    {
      xsane_save_ps_pdf_gray(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
    }
  }
  else /* color RGB */
  {
    xsane_save_ps_pdf_color(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
  }

  xsane_save_ps_create_page_trailer(outfile);

  if (ferror(outfile))
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_ps(FILE *outfile, FILE *imagefile, Image_info *image_info, float width, float height,
                  int paper_left_margin, int paper_bottom_margin, int paperwidth, int paperheight, int paper_orientation,
                  int flatdecode,
                  GtkProgressBar *progress_bar, int *cancel_save)
{
  DBG(DBG_proc, "xsane_save_ps\n");

  *cancel_save = 0;

  xsane_save_ps_create_document_header(outfile, 1 /* pages */, flatdecode);

  xsane_save_ps_page(outfile, 1 /* page */, 
                     imagefile, image_info, width, height,
                     paper_left_margin, paper_bottom_margin, paperwidth, paperheight, paper_orientation,
                     flatdecode,
                     progress_bar, cancel_save);

  xsane_save_ps_create_document_trailer(outfile, 0 /* we defined pages at beginning */);

  if (ferror(outfile))
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_save_pdf_create_document_header(FILE *outfile, struct pdf_xref *xref, int pages, int flatdecode)
{
 int i;

  DBG(DBG_proc, "xsane_save_pdf_create_document_header\n");

  fprintf(outfile, "%%PDF-1.4\n");
  fprintf(outfile, "\n");
  xref->obj[1] = ftell(outfile);
  fprintf(outfile, "1 0 obj\n");
  fprintf(outfile, "   << /Type /Catalog\n");
  fprintf(outfile, "      /Outlines 2 0 R\n");
  fprintf(outfile, "      /Pages 3 0 R\n");
  fprintf(outfile, "   >>\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");
  xref->obj[2] = ftell(outfile);
  fprintf(outfile, "2 0 obj\n");
  fprintf(outfile, "   << /Type /Outlines\n");
  fprintf(outfile, "      /Count 0\n");
  fprintf(outfile, "   >>\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");
  xref->obj[3] = ftell(outfile);
  fprintf(outfile, "3 0 obj\n");
  fprintf(outfile, "   << /Type /Pages\n");
  fprintf(outfile, "      /Kids [\n");
  for (i=0; i < pages; i++)
  {
    fprintf(outfile, "             %d 0 R\n", i * 2 + 4);
  }
  fprintf(outfile, "            ]\n");
  fprintf(outfile, "      /Count %d\n", pages);
  fprintf(outfile, "   >>\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* page = [1 .. pages] */
static void xsane_save_pdf_create_page_header(FILE *outfile, struct pdf_xref *xref, int page,
                                              Image_info *image_info,
                                              float width, float height,
                                              int paper_left_margin, int paper_bottom_margin,
                                              int paper_width, int paper_height,
                                              int paper_orientation, int flatdecode,
                                              GtkProgressBar *progress_bar)
{
 int position_left, position_bottom, box_left, box_bottom, box_right, box_top, depth;
 int left, bottom;
 float rad;

  DBG(DBG_proc, "xsane_save_pdf_create_page_header\n");

  switch (paper_orientation)
  {
    default:
    case 0: /* top left portrait */
      left   = 0.0;
      bottom = paper_height - height;
     break;

    case 1: /* top right portrait */
      left   = paper_width  - width;
      bottom = paper_height - height;
     break;

    case 2: /* bottom right portrait */
      left   = paper_width - width;
      bottom = 0.0;
     break;

    case 3: /* bottom left portrait */
      left   = 0.0;
      bottom = 0.0;
     break;

    case 4: /* center portrait */
      left   = paper_width  / 2.0 - width  / 2.0;
      bottom = paper_height / 2.0 - height / 2.0;
     break;


    case 8: /* top left landscape */
      left   = 0.0;
      bottom = paper_width - height;
     break;

    case 9: /* top right landscape */
      left   = paper_height - width;
      bottom = paper_width  - height;
     break;

    case 10: /* bottom right landscape */
      left   = paper_height - width;
      bottom = 0.0;
     break;

    case 11: /* bottom left landscape */
      left   = 0.0;
      bottom = 0.0;
     break;

    case 12: /* center landscape */
      left   = paper_height / 2.0 - width  / 2.0;
      bottom = paper_width  / 2.0 - height / 2.0;
     break;
  }


  if (paper_orientation >= 8) /* rotate with 90 degrees - landscape mode */
  {
    rad = -M_PI_2; /* pi / 2 */
    position_left   = left   + paper_bottom_margin;
    position_bottom = bottom - paper_width - paper_left_margin;
    box_left        = paper_width - paper_left_margin - bottom - height;
    box_bottom      = left   + paper_bottom_margin;
    box_right       = box_left   + ceil(height);
    box_top         = box_bottom + ceil(width);
  }
  else /* do not rotate, portrait mode */
  {
    rad = 0;
    position_left   = left   + paper_left_margin;
    position_bottom = bottom + paper_bottom_margin;
    box_left        = left   + paper_left_margin;
    box_bottom      = bottom + paper_bottom_margin;
    box_right       = box_left   + ceil(width);
    box_top         = box_bottom + ceil(height);
  }

  depth = image_info->depth;

  if (depth > 8)
  {
    depth = 8;
  }

  xref->obj[page * 2 + 2] = ftell(outfile);
  fprintf(outfile, "%d 0 obj\n", page * 2 + 2);
  fprintf(outfile, "    << /Type /Page\n");
  fprintf(outfile, "       /Parent 3 0 R\n");
  fprintf(outfile, "       /MediaBox [%d %d %d %d]\n", box_left, box_bottom, box_right, box_top);
  fprintf(outfile, "       /Contents %d 0 R\n", page * 2 + 3);
  fprintf(outfile, "       /Resources << /ProcSet %d 0 R >>\n", page * 2 + 4);
  fprintf(outfile, "    >>\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");

  /* Offset of object 5, for xref */
  xref->obj[page * 2 + 3] = ftell(outfile);

  fprintf(outfile, "%d 0 obj\n", page * 2 + 3);
  fprintf(outfile, "    << /Length             >>\n");

  /* Position of the stream length, to be written later on */
  xref->slenp = ftell(outfile) - 15;

  fprintf(outfile, "stream\n");

  /* Start of the stream data */
  xref->slen = ftell(outfile);

  fprintf(outfile, "q\n");
  fprintf(outfile, "1 0 0 1 %d %d cm\n", position_left, position_bottom); /* translate */
  fprintf(outfile, "%f %f -%f %f 0 0 cm\n", cos(rad), sin(rad), sin(rad), cos(rad)); /* rotate */
  fprintf(outfile, "%f 0 0 %f 0 0 cm\n", width, height); /* scale */
  fprintf(outfile, "BI\n");
  fprintf(outfile, "  /W %d\n", image_info->image_width);
  fprintf(outfile, "  /H %d\n", image_info->image_height);

  if (image_info->colors == 3) /* what about RGBA here ? */
  {
    fprintf(outfile, "  /CS /RGB\n");
    fprintf(outfile, "  /BPC %d\n", depth);
  }
  else if (image_info->depth == 1) /* BW */
  {
    fprintf(outfile, "  /CS /G\n");
    fprintf(outfile, "  /BPC 1\n");
  }
  else /* gray */
  {
    fprintf(outfile, "  /CS /G\n");
    fprintf(outfile, "  /BPC 8\n");
  }

#ifdef HAVE_LIBZ
  if (flatdecode)
  {  
    fprintf(outfile, "  /F [/A85 /FlateDecode]\n");
  }
  else
  {  
    fprintf(outfile, "  /F /A85\n");
  }
#else
  fprintf(outfile, "  /F /A85\n");
#endif
  fprintf(outfile, "ID\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_save_pdf_create_document_trailer(FILE *outfile, struct pdf_xref *xref, int pages)
{
 struct tm *t;
 time_t tt;
 int i;

  /* PDF document trailer */

  /* Offset of object 6, for xref */
  xref->obj[pages * 2 + 4] = ftell(outfile);

  fprintf(outfile, "%d 0 obj\n", pages * 2 + 4);
  fprintf(outfile, "    [/PDF]\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");

  /* Offset of object 7, for xref */
  xref->obj[pages * 2 + 5] = ftell(outfile);

  fprintf(outfile, "%d 0 obj\n", pages * 2 + 5);
  fprintf(outfile, "   << /Title (XSane scanned image)\n");
  fprintf(outfile, "      /Creator (XSane version %s (sane %d.%d) - by Oliver Rauch)\n",
	  VERSION,
	  SANE_VERSION_MAJOR(xsane.sane_backend_versioncode),
	  SANE_VERSION_MINOR(xsane.sane_backend_versioncode));
  fprintf(outfile, "      /Producer (XSane %s)\n", VERSION);

  tt = time(NULL);
  t = gmtime(&tt);

  fprintf(outfile, "      /CreationDate (D:%04d%02d%02d%02d%02d%02d+00'00')\n",
	  1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
  fprintf(outfile, "   >>\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");

  /* Offset of xref, for startxref below */
  xref->xref = ftell(outfile);

  fprintf(outfile, "xref\n");
  fprintf(outfile, "0 %d\n", pages * 2 + 6);
  fprintf(outfile, "0000000000 65535 f \n");

  for (i=1; i <= pages * 2 + 5; i++)
  {
    fprintf(outfile, "%010lu 00000 n \n", xref->obj[i]);
  }

  fprintf(outfile, "\n");
  fprintf(outfile, "trailer\n");
  fprintf(outfile, "    << /Size %d\n", pages * 2 + 6);
  fprintf(outfile, "       /Root 1 0 R\n");
  fprintf(outfile, "       /Info %d 0 R\n", pages * 2 + 5);
  fprintf(outfile, "    >>\n");
  fprintf(outfile, "startxref\n");
  fprintf(outfile, "%lu\n", xref->xref);
  fprintf(outfile, "%%%%EOF\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_save_pdf_create_page_trailer(FILE *outfile, struct pdf_xref *xref)
{
  /* PDF page trailer */
  fprintf(outfile, "EI\n");
  fprintf(outfile, "Q\n");

  /* Go back and write the length of the stream */
  xref->slen = ftell(outfile) - xref->slen - 1;
  fseek(outfile, xref->slenp, SEEK_SET);
  fprintf(outfile, "%lu", xref->slen);
  fseek(outfile, 0L, SEEK_END);

  fprintf(outfile, "endstream\n");
  fprintf(outfile, "endobj\n");
  fprintf(outfile, "\n");
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_pdf_page(FILE *outfile, struct pdf_xref *xref, int page,
                        FILE *imagefile, Image_info *image_info, float width, float height,
                        int paper_left_margin, int paper_bottom_margin, int paperwidth, int paperheight, int paper_orientation,
                        int flatdecode,
                        GtkProgressBar *progress_bar, int *cancel_save)
{

  DBG(DBG_proc, "xsane_save_pdf_page\n");

  xsane_save_pdf_create_page_header(outfile, xref, page,
                                    image_info, width, height,
			            paper_left_margin, paper_bottom_margin, paperwidth, paperheight, paper_orientation,
                                    flatdecode,
			            progress_bar);

  if (image_info->colors == 1) /* lineart, halftone, grayscale */
  {
    if (image_info->depth == 1) /* lineart, halftone */
    {
      xsane_save_ps_pdf_bw(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
    }
    else /* grayscale */
    {
      xsane_save_ps_pdf_gray(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
    }
  }
  else /* color RGB */
  {
    xsane_save_ps_pdf_color(outfile, imagefile, image_info, flatdecode, progress_bar, cancel_save);
  }

  xsane_save_pdf_create_page_trailer(outfile, xref);

  if (ferror(outfile))
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_pdf(FILE *outfile, FILE *imagefile, Image_info *image_info, float width, float height,
                   int paper_left_margin, int paper_bottom_margin, int paperwidth, int paperheight, int paper_orientation,
                   int flatdecode,
                   GtkProgressBar *progress_bar, int *cancel_save)
{
  struct pdf_xref xref;

  DBG(DBG_proc, "xsane_save_pdf\n");

  *cancel_save = 0;

  xsane_save_pdf_create_document_header(outfile, &xref, 1, flatdecode);

  xsane_save_pdf_page(outfile, &xref, 1,
                   imagefile, image_info, width, height,
                   paper_left_margin, paper_bottom_margin, paperwidth, paperheight, paper_orientation,
                   flatdecode,
                   progress_bar, cancel_save);

  xsane_save_pdf_create_document_trailer(outfile, &xref, 1);

  if (ferror(outfile))
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
    DBG(DBG_error, "%s\n", buf);
    xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
    *cancel_save = 1;
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */
#ifdef HAVE_LIBJPEG

typedef struct
{
  struct jpeg_error_mgr pub;/* "public" fields */
  int *cancel_save; 
} xsane_jpeg_error_mgr;

typedef xsane_jpeg_error_mgr *xsane_jpeg_error_mgr_ptr;

static void xsane_jpeg_error_exit(j_common_ptr cinfo)
{
 char buf[TEXTBUFSIZE];

  /* cinfo->err points to a xsane_jpeg_error_mgr struct */
  xsane_jpeg_error_mgr_ptr xsane_jpeg_error_mgr_data = (xsane_jpeg_error_mgr_ptr) cinfo->err;


  if (!*xsane_jpeg_error_mgr_data->cancel_save)
  {
    /* output original error message */
    (*cinfo->err->output_message) (cinfo);
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBJPEG);
    xsane_back_gtk_error(buf, TRUE);
  }

  *xsane_jpeg_error_mgr_data->cancel_save = 1;
}

int xsane_save_jpeg(FILE *outfile, FILE *imagefile, Image_info *image_info, int quality, GtkProgressBar *progress_bar, int *cancel_save)
{
 unsigned char *data;
 char buf[TEXTBUFSIZE];
 int components = 1;
 int x,y;
 int bytespp = 1;
 struct jpeg_compress_struct cinfo;
 xsane_jpeg_error_mgr jerr;
 JSAMPROW row_pointer[1];

  DBG(DBG_proc, "xsane_save_jpeg\n");

  *cancel_save = 0;

  if (image_info->colors == 3)
  {
    components = 3;
  }

  if (image_info->depth > 8)
  {
    bytespp = 2;
  }

  data = malloc(image_info->image_width * components * bytespp);

  if (!data)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_NO_MEM);
    xsane_back_gtk_error(buf, TRUE);
    return -1; /* error */
  }

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = xsane_jpeg_error_exit;
  jerr.cancel_save = cancel_save; 

  jpeg_create_compress(&cinfo);
  jpeg_stdio_dest(&cinfo, outfile);
  cinfo.image_width      = image_info->image_width;
  cinfo.image_height     = image_info->image_height;
  cinfo.input_components = components;
  if (image_info->colors == 3)
  {
    cinfo.in_color_space   = JCS_RGB;
  }
  else
  {
    cinfo.in_color_space   = JCS_GRAYSCALE;
  }
  jpeg_set_defaults(&cinfo);

  jpeg_set_quality(&cinfo, quality, TRUE);

  cinfo.density_unit     = 1; /* dpi */
  cinfo.X_density        = image_info->resolution_x;
  cinfo.Y_density        = image_info->resolution_y;

#if 0
  cinfo.smoothing_factor = 0.0; /* 0 .. 100 */
  cinfo.dct_method = JDCT_FLOAT; /* JDCT_ISLOW, JDCT_IFAST, JDCT_FLOAT */
#endif

  jpeg_start_compress(&cinfo, TRUE);

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (image_info->depth == 1)
    {
     int byte = 0;
     int mask = 128;

      for (x = 0; x < image_info->image_width; x++)
      {

        if ( (x % 8) == 0)
	{
          byte = fgetc(imagefile);
          mask = 128;
	}

        if (byte & mask)
        {
          data[x] = 0;
        }
        else
        {
          data[x] = 255;
        }
        mask >>= 1;
      }
    }
    else if (image_info->depth > 8) /* jpeg does not support 16 bits/sample, so we reduce it at first */
    {
      guint16 *data16 = (guint16 *) data;
      fread(data, components * 2, image_info->image_width, imagefile);
      for (x = 0; x < image_info->image_width * components; x++)
      {
        data[x] = data16[x] / 256;
      }

    }
    else /* 8 bits/sample */
    {
      fread(data, components, image_info->image_width, imagefile);
    }

    row_pointer[0] = data;
    jpeg_write_scanlines(&cinfo, row_pointer, 1);

    if (*cancel_save)
    {
      cinfo.image_height = y; /* correct image height */
      break;
    }
  }

  jpeg_finish_compress(&cinfo);
  free(data);

 return (*cancel_save);
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_LIBTIFF
/* pages = 0 => single page tiff, page = 0 */
/* pages > 0 => page = [1 .. pages] */
int xsane_save_tiff_page(TIFF *tiffile, int page, int pages, FILE *imagefile, Image_info *image_info, int quality,
                         GtkProgressBar *progress_bar, int *cancel_save)
{
 char *data;
 char buf[TEXTBUFSIZE];
 int y, w;
 int components;
 int compression;
 int bytes;
 struct tm *ptm;
 time_t now;

  DBG(DBG_proc, "xsane_save_tiff_page(%d/%d\n", page, pages);

  *cancel_save = 0;

  if (image_info->depth == 1)
  {
    compression = preferences.tiff_compression1_nr;
  }
  else if (image_info->depth == 8)
  {
    compression = preferences.tiff_compression8_nr;
  }
  else
  {
    compression = preferences.tiff_compression16_nr;
  }


  if (image_info->colors == 3)
  {
    components = 3;
  }
  else
  {
    components = 1;
  }

  if (image_info->depth <= 8)
  {
    bytes = 1;
  }
  else
  {
    bytes = 2;
  }

  data = (char *)_TIFFmalloc(image_info->image_width * components * bytes);

  if (!data)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_NO_MEM);
    xsane_back_gtk_error(buf, TRUE);
   return -1; /* error */
  }
  
  TIFFSetField(tiffile, TIFFTAG_IMAGEWIDTH, image_info->image_width);
  TIFFSetField(tiffile, TIFFTAG_IMAGELENGTH, image_info->image_height);
  TIFFSetField(tiffile, TIFFTAG_BITSPERSAMPLE, image_info->depth); 
  TIFFSetField(tiffile, TIFFTAG_SAMPLESPERPIXEL, components);
  TIFFSetField(tiffile, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
  TIFFSetField(tiffile, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  TIFFSetField(tiffile, TIFFTAG_COMPRESSION, compression);
  TIFFSetField(tiffile, TIFFTAG_SOFTWARE, "xsane");

  time(&now);
  ptm = localtime(&now);
  sprintf(buf, "%04d:%02d:%02d %02d:%02d:%02d", 1900+ptm->tm_year, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
  TIFFSetField(tiffile, TIFFTAG_DATETIME, buf);

  if (image_info->resolution_x > 0.0)
  {
    TIFFSetField(tiffile, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
    TIFFSetField(tiffile, TIFFTAG_XRESOLUTION, image_info->resolution_x);
    TIFFSetField(tiffile, TIFFTAG_YRESOLUTION, image_info->resolution_y);   
  }

  if (compression == COMPRESSION_DEFLATE)
  {
    TIFFSetField(tiffile, TIFFTAG_ZIPQUALITY, (int) preferences.tiff_zip_compression);
  }
  else if (compression == COMPRESSION_JPEG)
  {
    TIFFSetField(tiffile, TIFFTAG_JPEGQUALITY, quality);
  }

  if (image_info->colors == 3)
  {
    if (compression == COMPRESSION_JPEG)
    {
      TIFFSetField(tiffile, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_YCBCR);
      TIFFSetField(tiffile, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);  /* convert from RGB (to YCBCR) */
    }
    else /* no jpeg compression */
    {
      TIFFSetField(tiffile, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
    }
  }
  else
  {
    if (image_info->depth == 1) /* lineart */
    {
      TIFFSetField(tiffile, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
    }
    else /* grayscale */
    {
      TIFFSetField(tiffile, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); 
      /* we have to do nothing special for jpeg! */
    }
  }

  TIFFSetField(tiffile, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiffile, -1));

  if (pages)
  {
    TIFFSetField(tiffile, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
    TIFFSetField(tiffile, TIFFTAG_PAGENUMBER, page, pages);
  }

  w = TIFFScanlineSize(tiffile);

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }
    
    fread(data, 1, w, imagefile);

    if (TIFFWriteScanline(tiffile, data, y, 0) != 1)
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s", ERR_DURING_SAVE);
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

  if (pages)
  {
    TIFFWriteDirectory(tiffile);
  }

  _TIFFfree(data);
 return (*cancel_save);
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
int xsane_save_png(FILE *outfile, FILE *imagefile, Image_info *image_info, int compression, GtkProgressBar *progress_bar, int *cancel_save)
{
 png_structp png_ptr;
 png_infop png_info_ptr;
 png_bytep row_ptr;
 png_color_8 sig_bit;
 unsigned char *data;
 char buf[TEXTBUFSIZE];
 int colortype, components, byte_width;
 int y;

  DBG(DBG_proc, "xsane_save_png\n");

  *cancel_save = 0;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
  if (!png_ptr)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBPNG);
    xsane_back_gtk_error(buf, TRUE);
    return -1; /* error */
  }

  png_info_ptr = png_create_info_struct(png_ptr);
  if (!png_info_ptr)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBTIFF);
    xsane_back_gtk_error(buf, TRUE);
    return -1; /* error */
  }

  if (setjmp(png_ptr->jmpbuf))
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBPNG);
    xsane_back_gtk_error(buf, TRUE);
    png_destroy_write_struct(&png_ptr, (png_infopp) 0);
    return -1; /* error */
  }

  byte_width = image_info->image_width;

  if (image_info->colors == 4) /* RGBA */
  {
    components = 4;
    colortype = PNG_COLOR_TYPE_RGB_ALPHA;
  }
  else if (image_info->colors == 3) /* RGB */
  {
    components = 3;
    colortype = PNG_COLOR_TYPE_RGB;
  }
  else /* gray or black/white */
  {
    components = 1;
    colortype = PNG_COLOR_TYPE_GRAY;
  }
  
  png_init_io(png_ptr, outfile);
  png_set_compression_level(png_ptr, compression);
  png_set_IHDR(png_ptr, png_info_ptr, image_info->image_width, image_info->image_height, image_info->depth,
               colortype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

  if (image_info->colors >=3)
  {
    sig_bit.red   = image_info->depth;
    sig_bit.green = image_info->depth;
    sig_bit.blue  = image_info->depth;

    if (image_info->colors == 4)
    {
      sig_bit.alpha = image_info->depth;
    }

  }
  else
  {
    sig_bit.gray  = image_info->depth;

    if (image_info->depth == 1)
    {
      byte_width = (image_info->image_width+7)/8;
      png_set_invert_mono(png_ptr);
    }
  }

  png_set_sBIT(png_ptr, png_info_ptr, &sig_bit);
#if defined(PNG_pHYs_SUPPORTED)
  png_set_pHYs(png_ptr, png_info_ptr,
               image_info->resolution_x * 100.0 / 2.54,
               image_info->resolution_y * 100.0 / 2.54, PNG_RESOLUTION_METER);
#endif
  png_write_info(png_ptr, png_info_ptr);
  png_set_shift(png_ptr, &sig_bit);

  data = malloc(image_info->image_width * components);

  if (!data)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_NO_MEM);
    xsane_back_gtk_error(buf, TRUE);
    png_destroy_write_struct(&png_ptr, (png_infopp) 0);
    return -1; /* error */
  }

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    fread(data, components, byte_width, imagefile);

    row_ptr = data;
    png_write_rows(png_ptr, &row_ptr, 1); /* errors are caught by test sor setjmp(...) */

    if (*cancel_save)
    {
      break;
    }
  }

  free(data);
  png_write_end(png_ptr, png_info_ptr);
  png_destroy_write_struct(&png_ptr, (png_infopp) 0);

 return (*cancel_save);
}
#endif
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
int xsane_save_png_16(FILE *outfile, FILE *imagefile, Image_info *image_info, int compression, GtkProgressBar *progress_bar, int *cancel_save)
{
 png_structp png_ptr;
 png_infop png_info_ptr;
 png_bytep row_ptr;
 png_color_8 sig_bit; /* should be 16, but then I get a warning about wrong type */
 unsigned char *data;
 char buf[TEXTBUFSIZE];
 int colortype, components;
 int x,y;
 guint16 val;

  DBG(DBG_proc, "xsane_save_png16\n");

  *cancel_save = 0;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
  if (!png_ptr)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBPNG);
    xsane_back_gtk_error(buf, TRUE);
    return -1; /* error */
  }

  png_info_ptr = png_create_info_struct(png_ptr);
  if (!png_info_ptr)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBPNG);
    xsane_back_gtk_error(buf, TRUE);
    return -1; /* error */
  }

  if (setjmp(png_ptr->jmpbuf))
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_LIBPNG);
    xsane_back_gtk_error(buf, TRUE);
    png_destroy_write_struct(&png_ptr, (png_infopp) 0);
    return -1; /* error */
  }

  if (image_info->colors == 4) /* RGBA */
  {
    components = 4;
    colortype = PNG_COLOR_TYPE_RGB_ALPHA;
  }
  else if (image_info->colors == 3) /* RGB */
  {
    components = 3;
    colortype = PNG_COLOR_TYPE_RGB;
  }
  else /* gray or black/white */
  {
    components = 1;
    colortype = PNG_COLOR_TYPE_GRAY;
  }
  
  png_init_io(png_ptr, outfile);
  png_set_compression_level(png_ptr, compression);
  png_set_IHDR(png_ptr, png_info_ptr, image_info->image_width, image_info->image_height, 16,
               colortype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

  sig_bit.red   = image_info->depth;
  sig_bit.green = image_info->depth;
  sig_bit.blue  = image_info->depth;
  sig_bit.alpha = image_info->depth;
  sig_bit.gray  = image_info->depth;

  png_set_sBIT(png_ptr, png_info_ptr, &sig_bit);
  png_write_info(png_ptr, png_info_ptr);
  png_set_shift(png_ptr, &sig_bit);
  png_set_packing(png_ptr);

  data = malloc(image_info->image_width * components * 2);

  if (!data)
  {
    snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, ERR_NO_MEM);
    xsane_back_gtk_error(buf, TRUE);
    png_destroy_write_struct(&png_ptr, (png_infopp) 0);
    return -1; /* error */
  }

  for (y = 0; y < image_info->image_height; y++)
  {
    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    for (x = 0; x < image_info->image_width * components; x++) /* this must be changed in dependance of endianess */
    {
      fread(&val, 2, 1, imagefile);	/* get data in machine order */
      data[x*2+0] = val / 256;		/* write data in network order (MSB first) */
      data[x*2+1] = val & 255;
    }

    row_ptr = data;
    png_write_rows(png_ptr, &row_ptr, 1);
    if (*cancel_save)
    {
      break;
    }
  }

  free(data);
  png_write_end(png_ptr, png_info_ptr);
  png_destroy_write_struct(&png_ptr, (png_infopp) 0);

 return (*cancel_save);
}
#endif
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_pnm_16_ascii_gray(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x,y;
 guint16 val;
 int count = 0;

  DBG(DBG_proc, "xsane_save_pnm_16_ascii_gray\n");

  *cancel_save = 0;

  for (y = 0; y < image_info->image_height; y++)
  {
    for (x = 0; x < image_info->image_width; x++)
    {
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fprintf(outfile, "%d ", val);

      if (++count >= 10)
      {
        fprintf(outfile, "\n");
	count = 0;
      }
    }
    fprintf(outfile, "\n");

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    count = 0;

    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }
    if (*cancel_save)
    {
      break;
    }
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_pnm_16_ascii_color(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x,y;
 guint16 val;
 int count = 0;

  DBG(DBG_proc, "xsane_save_pnm_16_ascii_color\n");

  *cancel_save = 0;

  for (y = 0; y < image_info->image_height; y++)
  {
    for (x = 0; x < image_info->image_width; x++)
    {
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fprintf(outfile, "%d ", val);

      fread(&val, 2, 1, imagefile);
      fprintf(outfile, "%d ", val);

      fread(&val, 2, 1, imagefile);
      fprintf(outfile, "%d  ", val);

      if (++count >= 3)
      {
        fprintf(outfile, "\n");
	count = 0;
      }
    }
    fprintf(outfile, "\n");

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    count = 0;

    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (*cancel_save)
    {
      break;
    }
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_pnm_16_binary_gray(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x,y;
 guint16 val;

  DBG(DBG_proc, "xsane_save_pnm_16_binary_gray\n");

  *cancel_save = 0;

  for (y = 0; y < image_info->image_height; y++)
  {
    for (x = 0; x < image_info->image_width; x++)
    {
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fputc(val / 256, outfile); /* MSB fist */
      fputc(val & 255, outfile); /* LSB */
    }

    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_save_pnm_16_binary_color(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
 int x,y;
 guint16 val;

  DBG(DBG_proc, "xsane_save_pnm_16_binary_color\n");

  *cancel_save = 0;

  for (y = 0; y < image_info->image_height; y++)
  {
    for (x = 0; x < image_info->image_width; x++)
    {
      /* red */
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fputc(val / 256, outfile); /* MSB fist */
      fputc(val & 255, outfile); /* LSB */

      /* green */
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fputc(val / 256, outfile); /* MSB fist */
      fputc(val & 255, outfile); /* LSB */

      /* blue */
      fread(&val, 2, 1, imagefile); /* get data in machine order */
      fputc(val / 256, outfile); /* MSB fist */
      fputc(val & 255, outfile); /* LSB */
    }

    gtk_progress_bar_update(progress_bar, (float) y / image_info->image_height);
    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }


    if (ferror(outfile))
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s %s", ERR_DURING_SAVE, strerror(errno));
      DBG(DBG_error, "%s\n", buf);
      xsane_back_gtk_decision(ERR_HEADER_ERROR, (gchar **) error_xpm, buf, BUTTON_OK, NULL, TRUE /* wait */);
      *cancel_save = 1;
     break;
    }

    if (*cancel_save)
    {
      break;
    }
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_save_pnm_16(FILE *outfile, FILE *imagefile, Image_info *image_info, GtkProgressBar *progress_bar, int *cancel_save)
{
  DBG(DBG_proc, "xsane_save_pnm_16\n");

  *cancel_save = 0;

  xsane_write_pnm_header(outfile, image_info, preferences.save_pnm16_as_ascii);

  if (image_info->colors > 1)
  {
    if (preferences.save_pnm16_as_ascii)
    {
      xsane_save_pnm_16_ascii_color(outfile, imagefile, image_info, progress_bar, cancel_save);
    }
    else
    {
      xsane_save_pnm_16_binary_color(outfile, imagefile, image_info, progress_bar, cancel_save);
    }
  }
  else
  {
    if (preferences.save_pnm16_as_ascii)
    {
      xsane_save_pnm_16_ascii_gray(outfile, imagefile, image_info, progress_bar, cancel_save);
    }
    else
    {
      xsane_save_pnm_16_binary_gray(outfile, imagefile, image_info, progress_bar, cancel_save);
    }
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */

/* 0=ok, <0=error, 1=canceled */
int xsane_save_image_as_lineart(char *output_filename, char *input_filename, GtkProgressBar *progress_bar, int *cancel_save)
{
 FILE *outfile;
 FILE *infile;
 char buf[TEXTBUFSIZE];
 Image_info image_info;

  *cancel_save = 0;

  outfile = fopen(output_filename, "wb"); /* b = binary mode for win32 */

  if (outfile == 0)
  {
    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, output_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);
   return -2;
  }

  infile = fopen(input_filename, "rb"); /* read binary (b for win32) */
  if (infile == 0)
  {
   char buf[TEXTBUFSIZE];
    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, input_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);     

    fclose(outfile);
    remove(output_filename); /* remove already created output file */
   return -1;
  }

  xsane_read_pnm_header(infile, &image_info);

  xsane_save_grayscale_image_as_lineart(outfile, infile, &image_info, progress_bar, cancel_save);

  fclose(infile);
  fclose(outfile);

  if (*cancel_save) /* remove output file if saving has been canceled */
  {
    remove(output_filename);
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */
 
int xsane_save_image_as_text(char *output_filename, char *input_filename, GtkProgressBar *progress_bar, int *cancel_save)
{
 char *arg[1000];
 char buf[TEXTBUFSIZE];
 int argnr;
 pid_t pid;
 int i;
 int pipefd[2]; /* for progress communication with gocr */
 FILE *ocr_progress = NULL;
 
  DBG(DBG_proc, "xsane_save_image_as_text\n");
 
  argnr = xsane_parse_options(preferences.ocr_command, arg);
 
  arg[argnr++] = strdup(preferences.ocr_inputfile_option);
  arg[argnr++] = strdup(input_filename);
 
  arg[argnr++] = strdup(preferences.ocr_outputfile_option);
  arg[argnr++] = strdup(output_filename);
 
  if (preferences.ocr_use_gui_pipe)
  {
    if (!pipe(pipefd)) /* success */
    {
      DBG(DBG_info, "xsane_save_image_as_text: created pipe for progress communication\n");
 
      arg[argnr++] = strdup(preferences.ocr_gui_outfd_option);
 
      snprintf(buf, sizeof(buf),"%d", pipefd[1]);
      arg[argnr++] = strdup(buf);
    }
    else
    {
      DBG(DBG_info, "xsane_save_image_as_text: could not create pipe for progress communication\n");
      pipefd[0] = 0;
      pipefd[1] = 0;
    }
  }
  else
  {
    DBG(DBG_info, "xsane_save_image_as_text: no pipe for progress communication requested\n");
    pipefd[0] = 0;
    pipefd[1] = 0;
  }
 
  arg[argnr] = 0;

#ifndef HAVE_OS2_H 
  pid = fork();
 
  if (pid == 0) /* new process */
  {
   FILE *ipc_file = NULL;
 
    if (xsane.ipc_pipefd[0]) /* did we create the progress pipe? */
    {
      close(xsane.ipc_pipefd[0]); /* close reading end of pipe */
      ipc_file = fdopen(xsane.ipc_pipefd[1], "w");
    }
 
    if (pipefd[0]) /* did we create the progress pipe? */
    {
      close(pipefd[0]); /* close reading end of pipe */
    }
 
    DBG(DBG_info, "trying to change user id for new subprocess:\n");
    DBG(DBG_info, "old effective uid = %d\n", (int) geteuid());
    setuid(getuid());
    DBG(DBG_info, "new effective uid = %d\n", (int) geteuid());
 
 
    execvp(arg[0], arg); /* does not return if successfully */
    DBG(DBG_error, "%s %s\n", ERR_FAILED_EXEC_OCR_CMD, preferences.ocr_command);
 
    /* send error message via IPC pipe to parent process */
    if (ipc_file)
    {
      fprintf(ipc_file, "%s %s:\n%s", ERR_FAILED_EXEC_OCR_CMD, preferences.ocr_command, strerror(errno));
      fflush(ipc_file); /* make sure message is displayed */
      fclose(ipc_file);
    }
 
    _exit(0); /* do not use exit() here! otherwise gtk gets in trouble */
  }

#else
  pid = spawnvp(P_NOWAIT, arg[0], arg);
  if (pid == -1)
  {
   DBG(DBG_error, "%s %s\n", ERR_FAILED_EXEC_OCR_CMD, preferences.ocr_command);
  }
#endif
 
  if (pipefd[1])
  {
    close(pipefd[1]); /* close writing end of pipe */
    ocr_progress = fdopen(pipefd[0], "r"); /* open reading end of pipe as file */
  }
 
  for (i=0; i<argnr; i++)
  {
    free(arg[i]);
  }
 
  if (ocr_progress) /* pipe available */
  {
    gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);
 
    while (!feof(ocr_progress))
    {
     int progress, subprogress;
     float fprogress;
 
      fgets(buf, sizeof(buf), ocr_progress);
 
      if (!strncmp(preferences.ocr_progress_keyword, buf, strlen(preferences.ocr_progress_keyword)))
      {
        sscanf(buf + strlen(preferences.ocr_progress_keyword), "%d %d", &progress, &subprogress);
 
        snprintf(buf, sizeof(buf), "%s (%d:%d)", PROGRESS_OCR, progress, subprogress);
        gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), buf);
 
        fprogress = progress / 100.0; 
 
        if (fprogress < 0.0)
        {
          fprogress = 0.0;
        }
 
        if (fprogress > 11.0)
        {
          fprogress = 1.0;
        }
 
        gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), fprogress);
      }
 
      while (gtk_events_pending())
      {
        gtk_main_iteration();
      }
    }
 
    gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), "");
    gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);
  }
  else /* no pipe available */
  {
    while (pid)
    {
     int status = 0;
     pid_t pid_status = waitpid(pid, &status, WNOHANG);
 
      if (pid == pid_status)
      {
        pid = 0; /* ok, child process has terminated */
      }
 
      while (gtk_events_pending())
      {
        gtk_main_iteration();
      }
    }
  }
 
  if (pipefd[0])
  {
    fclose(ocr_progress); /* close reading end of pipe */
  }

 return (*cancel_save);
}                                                                                                                                                      
/* ---------------------------------------------------------------------------------------------------------------------- */

/* save image in destination file format. lineart images that are stored as grayscale image are reduced to lineart! */
int xsane_save_image_as(char *output_filename, char *input_filename, int output_format, GtkProgressBar *progress_bar, int *cancel_save)
{
 FILE *outfile;
 FILE *infile;
 char buf[TEXTBUFSIZE];
 Image_info image_info;
 char lineart_filename[PATH_MAX];
 int remove_input_file = FALSE;
  
  DBG(DBG_proc, "xsane_save_image_as(output_file=%s, input_file=%s, type=%d)\n", output_filename, input_filename, output_format);

  *cancel_save = 0;

  infile = fopen(input_filename, "rb"); /* read binary (b for win32) */
  if (infile == 0)
  {
   char buf[TEXTBUFSIZE];
    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, input_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);     

   return -1;
  }

  xsane_read_pnm_header(infile, &image_info);

  if ((image_info.reduce_to_lineart) && (output_format != XSANE_PNM))
  {
    DBG(DBG_info, "original image is a lineart => reduce to lineart\n");
    fclose(infile);
    xsane_back_gtk_make_path(sizeof(lineart_filename), lineart_filename, 0, 0, "xsane-conversion-", xsane.dev_name, ".pbm", XSANE_PATH_TMP);

    snprintf(buf, sizeof(buf), "%s: %s", PROGRESS_PACKING_DATA, output_filename);

    gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), buf);
    gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

    while (gtk_events_pending())
    {
      gtk_main_iteration();
    }

    xsane_save_image_as_lineart(lineart_filename, input_filename, progress_bar, cancel_save);

    input_filename = lineart_filename;
    remove_input_file = TRUE;

    infile = fopen(input_filename, "rb"); /* read binary (b for win32) */
    if (infile == 0)
    {
     char buf[TEXTBUFSIZE];
      snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, input_filename, strerror(errno));
      xsane_back_gtk_error(buf, TRUE);     

     return -1;
    }

    xsane_read_pnm_header(infile, &image_info);
  }

  snprintf(buf, sizeof(buf), "%s: %s", PROGRESS_SAVING_DATA, output_filename);

  gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), buf);
  gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }


#ifdef HAVE_LIBTIFF
  if (output_format == XSANE_TIFF)		/* routines that want to have filename  for saving */
  {
   TIFF *tiffile;

    if (xsane_create_secure_file(output_filename)) /* remove possibly existing symbolic links for security */
    {
      snprintf(buf, sizeof(buf), "%s %s %s\n", ERR_DURING_SAVE, ERR_CREATE_SECURE_FILE, output_filename);
      xsane_back_gtk_error(buf, TRUE);
     return -1; /* error */
    }

    tiffile = TIFFOpen(output_filename, "w");
    if (!tiffile)
    {
      snprintf(buf, sizeof(buf), "%s %s %s\n", ERR_DURING_SAVE, ERR_OPEN_FAILED, output_filename);
      xsane_back_gtk_error(buf, TRUE);
     return -1; /* error */
    }

    xsane_save_tiff_page(tiffile, 0, 0, infile, &image_info, preferences.jpeg_quality, progress_bar, cancel_save);

    TIFFClose(tiffile);
  }
  else							/* routines that want to have filedescriptor for saving */
#endif /* HAVE_LIBTIFF */
  {
    if (xsane_create_secure_file(output_filename)) /* remove possibly existing symbolic links for security */
    {
      snprintf(buf, sizeof(buf), "%s %s %s\n", ERR_DURING_SAVE, ERR_CREATE_SECURE_FILE, output_filename);
      xsane_back_gtk_error(buf, TRUE);
     return -1; /* error */
    }

    outfile = fopen(output_filename, "wb"); /* b = binary mode for win32 */

    if (outfile != 0)
    {
      switch(output_format)
      {
        case XSANE_PNM:
          if (image_info.reduce_to_lineart)
          {
            xsane_save_grayscale_image_as_lineart(outfile, infile, &image_info, progress_bar, cancel_save);
          }
          else
          {
            xsane_copy_file(outfile, infile, progress_bar, cancel_save);
          }
         break;

#ifdef HAVE_LIBJPEG
        case XSANE_JPEG:
          xsane_save_jpeg(outfile, infile, &image_info, preferences.jpeg_quality, progress_bar, cancel_save);
         break; /* switch format == XSANE_JPEG */
#endif

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
        case XSANE_PNG:
          if (image_info.depth <= 8)
          {
            xsane_save_png(outfile, infile, &image_info, preferences.png_compression, progress_bar, cancel_save);
          }
          else
          {
            xsane_save_png_16(outfile, infile, &image_info, preferences.png_compression, progress_bar, cancel_save);
          }
         break; /* switch format == XSANE_PNG */
#endif
#endif

        case XSANE_PNM16:
          xsane_save_pnm_16(outfile, infile, &image_info, progress_bar, cancel_save);
         break; /* switch fomat == XSANE_PNM16 */

        case XSANE_PS: /* save postscript, use original size */
        { 
         float imagewidth, imageheight;

           imagewidth  = 72.0 * image_info.image_width/image_info.resolution_x; /* width in 1/72 inch */
           imageheight = 72.0 * image_info.image_height/image_info.resolution_y; /* height in 1/72 inch */

            xsane_save_ps(outfile, infile,
                          &image_info,
                          imagewidth, imageheight,
                          0, /* paper_left_margin */
                          0, /* paper_bottom_margin */
                          (int) imagewidth, /* paper_width */
                          (int) imageheight, /* paper_height */
                          0 /* portrait top left */,
                          preferences.save_ps_flatdecoded,
                          progress_bar,
                          cancel_save);
        }
        break; /* switch format == XSANE_PS */

        case XSANE_PDF: /* save PDF, use original size */
        { 
         float imagewidth, imageheight;

           imagewidth  = 72.0 * image_info.image_width/image_info.resolution_x; /* width in 1/72 inch */
           imageheight = 72.0 * image_info.image_height/image_info.resolution_y; /* height in 1/72 inch */

            xsane_save_pdf(outfile, infile,
                          &image_info,
                          imagewidth, imageheight,
                          0, /* paper_left_margin */
                          0, /* paper_bottom_margin */
                          (int) imagewidth, /* paper_width */
                          (int) imageheight, /* paper_height */
                          0 /* portrait top left */,
                          preferences.save_pdf_flatdecoded,
                          progress_bar,
                          cancel_save);
        }
        break; /* switch format == XSANE_PDF */

        case XSANE_TEXT: /* save as text using ocr program like gocr/jocr */
        {
          xsane_save_image_as_text(output_filename, input_filename, progress_bar, cancel_save);
        }
        break; /* switch format == XSANE_TEXT */

        default:
          snprintf(buf, sizeof(buf),"%s", ERR_UNKNOWN_SAVING_FORMAT);
          xsane_back_gtk_error(buf, TRUE);

          fclose(outfile);
          fclose(infile);

          remove(output_filename); /* no usable output: remove output file  */

          if (remove_input_file)
          {
            remove(input_filename); /* remove lineart pbm file  */
          }

          gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), "");
          gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

          while (gtk_events_pending())
          {
            gtk_main_iteration();
          }

         return -2;
         break; /* switch format == default */
      }
      fclose(outfile);
    }
    else
    {
      snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, output_filename, strerror(errno));
      xsane_back_gtk_error(buf, TRUE);

      fclose(infile);

      if (remove_input_file)
      {
        remove(input_filename); /* remove lineart pbm file  */
      }

      gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), "");
      gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

      while (gtk_events_pending())
      {
        gtk_main_iteration();
      }

     return -2;
    }
  }

  fclose (infile);

  if (remove_input_file)
  {
    remove(input_filename); /* remove lineart pbm file  */
  }

  if (*cancel_save) /* remove output file if saving has been canceled */
  {
    remove(output_filename);
  }

  gtk_progress_set_format_string(GTK_PROGRESS(progress_bar), "");
  gtk_progress_bar_update(GTK_PROGRESS_BAR(progress_bar), 0.0);

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

 return (*cancel_save);
}

/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_ANY_GIMP
static int xsane_decode_devname(const char *encoded_devname, int n, char *buf)
{
 char *dst, *limit;
 const char *src;
 char ch, val;

  DBG(DBG_proc, "xsane_decode_devname\n");

  limit = buf + n;
  for (src = encoded_devname, dst = buf; *src; ++dst)
  {
    if (dst >= limit)
    {
      return -1;
    }

    ch = *src++;
    /* don't use the ctype.h macros here since we don't want to allow anything non-ASCII here... */
    if (ch != '-')
    {
      *dst = ch;
    }
    else /* decode */
    {
      ch = *src++;
      if (ch == '-')
      {
        *dst = ch;
      }
      else
      {
        if (ch >= 'a' && ch <= 'f')
        {
          val = (ch - 'a') + 10;
        }
        else
        {
          val = (ch - '0');
        }
        val <<= 4;

        ch = *src++;
        if (ch >= 'a' && ch <= 'f')
        {
          val |= (ch - 'a') + 10;
        }
        else
        {
          val |= (ch - '0');
        }

        *dst = val;

        ++src;    /* simply skip terminating '-' for now... */
      }
    }
  }

  if (dst >= limit)
  {
    return -1;
  }

  *dst = '\0';
 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_encode_devname(const char *devname, int n, char *buf)
{
 static const char hexdigit[] = "0123456789abcdef";
 char *dst, *limit;
 const char *src;
 char ch;

  DBG(DBG_proc, "xsane_encode_devname\n");

  limit = buf + n;
  for (src = devname, dst = buf; *src; ++src)
  {
    if (dst >= limit)
    {
      return -1;
    }

    ch = *src;
    /* don't use the ctype.h macros here since we don't want to allow anything non-ASCII here... */
    if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
    {
      *dst++ = ch;
    }
    else /* encode */
    {
      if (dst + 4 >= limit)
      {
        return -1;
      }

      *dst++ = '-';
      if (ch == '-')
      {
        *dst++ = '-';
      }
      else
      {
        *dst++ = hexdigit[(ch >> 4) & 0x0f];
        *dst++ = hexdigit[(ch >> 0) & 0x0f];
        *dst++ = '-';
      }
    }
  }

  if (dst >= limit)
  {
    return -1;
  }

  *dst = '\0';
 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_gimp_query(void)
{
 static GimpParamDef args[] =
 {
     {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
 };
 static GimpParamDef *return_vals = NULL;
 static int nargs = sizeof(args) / sizeof(args[0]);
 static int nreturn_vals = 0;
 char mpath[1024];
 char name[1024];
 size_t len;
 int i, j;

  DBG(DBG_proc, "xsane_gimp_query\n");

  snprintf(name, sizeof(name), "%s", xsane.prog_name);
#ifdef GIMP_CHECK_VERSION
# if GIMP_CHECK_VERSION(1,1,9)
  snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU_DIALOG);
# else
  snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU_DIALOG_OLD);
# endif
#else
  snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU_DIALOG_OLD);
#endif
  gimp_install_procedure(name,
			 XSANE_GIMP_INSTALL_BLURB,
			 XSANE_GIMP_INSTALL_HELP,
			 XSANE_AUTHOR,
			 XSANE_COPYRIGHT,
			 XSANE_DATE,
			 mpath,
			 0, /* "RGB, GRAY", */
			 GIMP_EXTENSION,
			 nargs, nreturn_vals,
			 args, return_vals);

  sane_init(&xsane.sane_backend_versioncode, (void *) xsane_authorization_callback);
  if (SANE_VERSION_MAJOR(xsane.sane_backend_versioncode) != SANE_V_MAJOR)
  {
    DBG(DBG_error0, "\n\n"
                    "%s %s:\n"
                    "  %s\n"
                    "  %s %d\n"
                    "  %s %d\n"
                    "%s\n\n",
                     xsane.prog_name, ERR_ERROR,
                     ERR_MAJOR_VERSION_NR_CONFLICT,
                     ERR_XSANE_MAJOR_VERSION, SANE_V_MAJOR,
                     ERR_BACKEND_MAJOR_VERSION, SANE_VERSION_MAJOR(xsane.sane_backend_versioncode),
                     ERR_PROGRAM_ABORTED);      
    return;
  }

  sane_get_devices(&xsane.devlist, SANE_FALSE);

  for (i = 0; xsane.devlist[i]; ++i)
  {
    snprintf(name, sizeof(name), "%s-", xsane.prog_name);
    if (xsane_encode_devname(xsane.devlist[i]->name, sizeof(name) - 6, name + 6) < 0)
    {
      continue;	/* name too long... */
    }

#ifdef GIMP_CHECK_VERSION
# if GIMP_CHECK_VERSION(1,1,9)
    snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU);
# else
    snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU_OLD);
# endif
#else
    snprintf(mpath, sizeof(mpath), "%s", XSANE_GIMP_MENU_OLD);
#endif
    len = strlen(mpath);
    for (j = 0; xsane.devlist[i]->name[j]; ++j)
    {
      if (xsane.devlist[i]->name[j] == '/')
      {
        mpath[len++] = '\'';
      }
      else
      {
        mpath[len++] = xsane.devlist[i]->name[j];
      }
    }
    mpath[len++] = '\0';

    gimp_install_procedure(name,
                           XSANE_GIMP_INSTALL_BLURB,
                           XSANE_GIMP_INSTALL_HELP,
                           XSANE_AUTHOR,
                           XSANE_COPYRIGHT,
                           XSANE_DATE,
                           mpath,
                           0, /* "RGB, GRAY", */
                           GIMP_EXTENSION,
                           nargs, nreturn_vals,
                           args, return_vals);
  }

  sane_exit();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef HAVE_GIMP_2
static void xsane_gimp_run(const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals)
{
 GimpRunMode run_mode;
#else /* GIMP-1.x */
static void xsane_gimp_run(char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals)
{
 GimpRunModeType run_mode;
#endif

 static GimpParam values[2];
 char devname[1024];
 char *args[2];
 int nargs;

  DBG(DBG_proc, "xsane_gimp_run\n");

  run_mode = param[0].data.d_int32;
  xsane.mode = XSANE_GIMP_EXTENSION;
  xsane.xsane_mode = XSANE_SAVE;

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_CALLING_ERROR;

  nargs = 0;
  args[nargs++] = "xsane";

  xsane.selected_dev = -1;
  if (strncmp(name, "xsane-", 6) == 0)
  {
    if (xsane_decode_devname(name + 6, sizeof(devname), devname) < 0)
    {
      return;			/* name too long */
    }
    args[nargs++] = devname;
  }

  switch (run_mode)
  {
    case GIMP_RUN_INTERACTIVE:
#ifdef HAVE_GIMP_2
      gimp_extension_ack();
#endif
      xsane_interface(nargs, args);
      values[0].data.d_status = GIMP_PDB_SUCCESS;
      break;

    case GIMP_RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      break;

    default:
      break;
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void null_print_func(gchar *msg)
{
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_transfer_to_gimp(char *input_filename, GtkProgressBar *progress_bar, int *cancel_save)
{
 int remaining;
 size_t tile_size;
 GimpImageType image_type    = GIMP_GRAY;
 GimpImageType drawable_type = GIMP_GRAY_IMAGE;
 gint32 layer_ID;
 gint32 image_ID;
 GimpDrawable *drawable;
 guchar *tile;
 GimpPixelRgn region;
 unsigned tile_offset;
 int i, x, y;
 Image_info image_info;
 FILE *imagefile;

  DBG(DBG_info, "xsane_transer_to_gimp\n");

  *cancel_save = 0;

  imagefile = fopen(input_filename, "rb"); /* read binary (b for win32) */
  if (imagefile == 0)
  {
   char buf[TEXTBUFSIZE];
    snprintf(buf, sizeof(buf), "%s `%s': %s", ERR_OPEN_FAILED, input_filename, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);     

   return -1;
  }

  xsane_read_pnm_header(imagefile, &image_info);

  x = 0;
  y = 0; 
  tile_offset = 0;
  tile_size = image_info.image_width * gimp_tile_height();

  if (image_info.colors == 3) /* RGB */
  {
    tile_size *= 3;  /* 24 bits/pixel RGB */
    image_type    = GIMP_RGB;
    drawable_type = GIMP_RGB_IMAGE;
  }
  else if (image_info.colors == 4) /* RGBA */
  {
    tile_size *= 4;  /* 32 bits/pixel RGBA */
    image_type    = GIMP_RGB;
    drawable_type = GIMP_RGBA_IMAGE; /* interpret infrared as alpha */
  }
  /* colors == 0/1 is predefined */
 
  image_ID = gimp_image_new(image_info.image_width, image_info.image_height, image_type);
 
/* the following is supported since gimp-1.1.? */
#ifdef GIMP_HAVE_RESOLUTION_INFO
  if (image_info.resolution_x > 0)
  {
    gimp_image_set_resolution(image_ID, image_info.resolution_x, image_info.resolution_y);
  }
/*          gimp_image_set_unit(image_ID, unit?); */
#endif
 
  layer_ID = gimp_layer_new(image_ID, "Background", image_info.image_width, image_info.image_height, drawable_type, 100.0, GIMP_NORMAL_MODE);
  gimp_image_add_layer(image_ID, layer_ID, 0);
  drawable = gimp_drawable_get(layer_ID);
  gimp_pixel_rgn_init(&region, drawable, 0, 0, drawable->width, drawable->height, TRUE, FALSE);
  tile = g_new(guchar, tile_size);
 

  if (image_info.colors == 1) /* gray */
  {
    switch (image_info.depth)
    {
      case 1: /* 1 bit gray => conversion to 8 bit gray */
        for (i = 0; i < ( (image_info.image_width + 7) / 8) * image_info.image_height; ++i)
        {
         u_char mask;
         int j;

          mask = fgetc(imagefile);
          for (j = 7; j >= 0; --j)
          {
            u_char gl = (mask & (1 << j)) ? 0x00 : 0xff;
            tile[tile_offset++] = gl;

            x++;

            if (x >= image_info.image_width)
            {
             int tile_height = gimp_tile_height();

              x = 0;
              y++;

              if (y % tile_height == 0)
              {
                gimp_pixel_rgn_set_rect(&region, tile, 0, y - tile_height, image_info.image_width, tile_height);
                tile_offset = 0;
              }

              gtk_progress_bar_update(progress_bar, (float) y / image_info.image_height); /* update progress bar */
              while (gtk_events_pending())
              {
                gtk_main_iteration();
              }

              break; /* leave for j loop */
            }
          }

          if (*cancel_save)
          {
            break;
          }
        }
       break; /* leave switch depth 1 */

      case 8: /* 8 bit gray */
      case 16: /* 16 bit gray already has been reduced to 8 bit */
        for (i = 0; i < image_info.image_width * image_info.image_height; ++i)
        {
          tile[tile_offset++] = fgetc(imagefile);
          x++;

          if (x >= image_info.image_width)
          {
           int tile_height = gimp_tile_height();

            x = 0;
            y++;

            if (y % tile_height == 0)
            {
              gimp_pixel_rgn_set_rect(&region, tile, 0, y - tile_height, image_info.image_width, tile_height);
              tile_offset = 0;
            }

            gtk_progress_bar_update(progress_bar, (float) y / image_info.image_height); /* update progress bar */
            while (gtk_events_pending())
            {
              gtk_main_iteration();
            }
          }

          if (*cancel_save)
          {
            break;
          }
        }
       break; /* leave switch depth */

      default: /* bad depth */
       break; /* default */
    }
  }
  else if (image_info.colors == 3) /* RGB */
  {
    switch (image_info.depth)
    {
      case 8: /* 8 bit RGB */
      case 16: /* 16 bit RGB already has been reduced to 8 bit */
        for (i = 0; i < image_info.image_width * image_info.image_height*3; ++i)
        {
          tile[tile_offset++] = fgetc(imagefile);
          if (tile_offset % 3 == 0)
          {
            x++;

            if (x >= image_info.image_width)
            {
             int tile_height = gimp_tile_height();

              x = 0;
              y++;

              if (y % tile_height == 0)
              {
                gimp_pixel_rgn_set_rect(&region, tile, 0, y - tile_height, image_info.image_width, tile_height);
                tile_offset = 0;
              }

              gtk_progress_bar_update(progress_bar, (float) y / image_info.image_height); /* update progress bar */
              while (gtk_events_pending())
              {
                gtk_main_iteration();
              }
            }
          }

          if (*cancel_save)
          {
            break;
          }
        }
       break; /* case 8 */

      default: /* bad depth */
       break; /* default */
    }
  }
#ifdef SUPPORT_RGBA
  else if (image_info.colors == 4) /* RGBA */
  {
   int i;

    switch (image_info.depth)
    {
      case 8: /* 8 bit RGBA */
      case 16: /* 16 bit RGBA already has been reduced to 8 bit */
        for (i = 0; i < image_info.image_width * image_info.image_height * 4; ++i)
        {
          tile[tile_offset++] = fgetc(imagefile);
          if (tile_offset % 4 == 0)
          {
            x++;

            if (x >= image_info.image_width)
            {
             int tile_height = gimp_tile_height();

              x = 0;
              y++;

              if (y % tile_height == 0)
              {
                gimp_pixel_rgn_set_rect(&region, tile, 0, y - tile_height, image_info.image_width, tile_height);
                tile_offset = 0;
              }

              gtk_progress_bar_update(progress_bar, (float) y / image_info.image_height); /* update progress bar */
              while (gtk_events_pending())
              {
                gtk_main_iteration();
              }
            }
          }

          if (*cancel_save)
          {
            break;
          }
        }
       break;

      default: /* bad depth */
       break;
    }
  }
#endif

/* scan_done part */
  if (y > image_info.image_height)
  {
    y = image_info.image_height;
  }
 
  remaining = y % gimp_tile_height();

  if (remaining)
  {
    gimp_pixel_rgn_set_rect(&region, tile, 0, y - remaining, image_info.image_width, remaining);
  }

  gimp_drawable_flush(drawable);
  gimp_display_new(image_ID);
  gimp_drawable_detach(drawable);
  g_free(tile);
  tile = 0;

  fclose(imagefile);

 return 0;
}
#endif /* HAVE_ANY_GIMP */ 

/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------- */

#ifdef XSANE_ACTIVATE_EMAIL

/* character base of base64 coding */
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/* ---------------------------------------------------------------------------------------------------------------------- */

static void write_3chars_as_base64(unsigned char c1, unsigned char c2, unsigned char c3, int pads, int fd_socket)
{
 char buf[4];

  buf[0] = base64[c1>>2]; /* wirte bits 7-2 of first char */
  buf[1] = base64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)]; /* write bits 1,0 of first and bits 7-4 of second char */

  if (pads == 2) /* only one byte used */
  {
    buf[2] = '='; /* char not used */
    buf[3] = '='; /* char not used */
  }
  else if (pads) /* only two bytes used */
  {
    buf[2] = base64[((c2 & 0xF) << 2)]; /* write bits 3-0 of second char */
    buf[3] = '='; /* char not used */
  }
  else
  {
    buf[2] = base64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; /* write bits 3-0 of second and bits 7,6 of third char */
    buf[3] = base64[c3 & 0x3F]; /* write bits 5-0 of third char as lsb */
  }

  write(fd_socket, buf, 4);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_string_base64(int fd_socket, unsigned char *string, int len)
{
 int i;
 int pad;
 unsigned char c1, c2, c3;

  for (i = 0; i < len; i+=3)
  {
    c1 = string[i];
    c2 = string[i+1];
    c3 = string[i+2];

    pad = i - len + 3;

    if (pad < 0)
    {
      pad = 0;
    }
    else if (pad)
    {
      c3 = 0;

      if (pad == 2)
      {
        c2 = 0;
      }
    }

    write_3chars_as_base64(c1, c2, c3, pad, fd_socket);
  }
  write(fd_socket, "\r\n", 2);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_base64(int fd_socket, FILE *infile) 
{
 int c1, c2, c3;
 int pos = 0;

  while ((c1 = getc(infile)) != EOF)
  {
    c2 = getc(infile);
    if (c2 == EOF)
    {
      write_3chars_as_base64(c1, 0, 0, 2, fd_socket);
    }
    else
    {
      c3 = getc(infile);
      if (c3 == EOF)
      {
        write_3chars_as_base64(c1, c2, 0, 1, fd_socket);
      }
      else
      {
        write_3chars_as_base64(c1, c2, c3, 0, fd_socket);
      }
    }

    pos += 4;
    if (pos > 71)
    {
      write(fd_socket, "\n", 1);
      
      pos = 0;
    }

    xsane.email_progress_bytes += 3;
    if ((int)  ((xsane.email_progress_bytes * 100) / xsane.email_progress_size) != (int) (xsane.email_progress_val * 100))
    {
      xsane.email_progress_val = (float) xsane.email_progress_bytes / xsane.email_progress_size;
      xsane_front_gtk_email_project_update_lockfile_status();
    }
  }

  if (pos)
  {
    write(fd_socket, "\n", 1);
  }

  xsane.email_progress_val = 1.0;
  xsane_front_gtk_email_project_update_lockfile_status();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_header(int fd_socket, char *from, char *reply_to, char *to, char *subject, char *boundary, int related)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "From: %s\n", from);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Reply-To: %s\n", reply_to);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "To: %s\n", to);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Subject: %s\n", subject);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "MIME-Version: 1.0\n");
  write(fd_socket, buf, strlen(buf));

  if (related) /* related means that we need a special link in the html part to display the image */
  {
    snprintf(buf, sizeof(buf), "Content-Type: multipart/related;\n");
    write(fd_socket, buf, strlen(buf));
  }
  else
  {
    snprintf(buf, sizeof(buf), "Content-Type: multipart/mixed;\n");
    write(fd_socket, buf, strlen(buf));
  }

  snprintf(buf, sizeof(buf), " boundary=\"%s\"\n\n", boundary);
  write(fd_socket, buf, strlen(buf));
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_footer(int fd_socket, char *boundary)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "--%s--\n", boundary);
  write(fd_socket, buf, strlen(buf));
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_mime_ascii(int fd_socket, char *boundary)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "--%s\n", boundary);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Type: text/plain;\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "        charset=\"iso-8859-1\"\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Transfer-Encoding: 8bit\n\n");
  write(fd_socket, buf, strlen(buf));
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_mime_html(int fd_socket, char *boundary)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "--%s\n", boundary);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Type: text/html;\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "        charset=\"us-ascii\"\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Transfer-Encoding: 7bit\n\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "<html>\n");
  write(fd_socket, buf, strlen(buf));
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_attach_image(int fd_socket, char *boundary, char *content_id, char *content_type, FILE *infile, char *filename)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "--%s\n", boundary);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Type: %s\n", content_type);
  write(fd_socket, buf, strlen(buf));

  if (content_id)
  {
    snprintf(buf, sizeof(buf), "Content-ID: <%s>\n", content_id);
    write(fd_socket, buf, strlen(buf));
  }

  snprintf(buf, sizeof(buf), "Content-Transfer-Encoding: base64\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Disposition: inline;\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "        filename=\"%s\"\n", filename);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "\n");
  write(fd_socket, buf, strlen(buf));

  write_base64(fd_socket, infile);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void write_email_attach_file(int fd_socket, char *boundary, FILE *infile, char *filename)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "--%s\n", boundary);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Type: application/octet-stream\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "        name=\"%s\"\n", filename);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Transfer-Encoding: base64\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "Content-Disposition: attachment;\n");
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "        filename=\"%s\"\n", filename);
  write(fd_socket, buf, strlen(buf));

  snprintf(buf, sizeof(buf), "\n");
  write(fd_socket, buf, strlen(buf));

  write_base64(fd_socket, infile);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* returns fd_socket if sucessfull, < 0 when error occured */
int open_socket(char *server, int port)
{
 int fd_socket;
 struct sockaddr_in sin;
 struct hostent *he;

  he = gethostbyname(server);
  if (!he)
  {
    DBG(DBG_error, "open_socket: Could not get hostname of \"%s\"\n", server);
   return -1;
  }
  else
  {
    DBG(DBG_info, "open_socket: connecting to \"%s\" = %d.%d.%d.%d\n",
        he->h_name,
        (unsigned char) he->h_addr_list[0][0],
        (unsigned char) he->h_addr_list[0][1],
        (unsigned char) he->h_addr_list[0][2],
        (unsigned char) he->h_addr_list[0][3]);
  }
 
  if (he->h_addrtype != AF_INET)
  {
    DBG(DBG_error, "open_socket: Unknown address family: %d\n", he->h_addrtype);
   return -1;
  }

  fd_socket = socket(AF_INET, SOCK_STREAM, 0);

  if (fd_socket < 0)
  {
    DBG(DBG_error, "open_socket: Could not create socket: %s\n", strerror(errno));
   return -1;
  }

/*  setsockopt (dev->ctl, level, TCP_NODELAY, &on, sizeof (on)); */

  sin.sin_port = htons(port);
  sin.sin_family = AF_INET;
  memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);

  if (connect(fd_socket, &sin, sizeof(sin)))
  {
    DBG(DBG_error, "open_socket: Could not connect with port %d of socket: %s\n", ntohs(sin.sin_port), strerror(errno));
   return -1;
  }

  DBG(DBG_info, "open_socket: Connected with port %d\n", ntohs(sin.sin_port));

 return fd_socket;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* returns 0 if success */
/* not only a write routine, also reads data */
int pop3_login(int fd_socket, char *user, char *passwd)
{
 char buf[1024];
 int len;

  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  snprintf(buf, sizeof(buf), "USER %s\r\n", user);
  DBG(DBG_info2, "> USER xxx\n");
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);
  if (buf[0] != '+')
  {
    return -1;
  }

  snprintf(buf, sizeof(buf), "PASS %s\r\n", passwd);
  DBG(DBG_info2, "> PASS xxx\n");
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);
  if (buf[0] != '+')
  {
    return -1;
  }

  snprintf(buf, sizeof(buf), "QUIT\r\n");
  DBG(DBG_info2, "> QUIT\n");
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int asmtp_authentication(int fd_socket, int auth_type, char *user, char *passwd)
{
 int len;
 char buf[1024];

  DBG(DBG_proc, "asmtp_authentication\n");

  switch (auth_type)
  {
    case EMAIL_AUTH_ASMTP_PLAIN:
      snprintf(buf, sizeof(buf), "AUTH PLAIN ");
      DBG(DBG_info2, "> %s\\0(USER)\\0(PASSWORD)\n", buf);
      write(fd_socket, buf, strlen(buf));
      snprintf(buf, sizeof(buf), "%c%s%c%s", 0, user, 0, passwd);
      write_string_base64(fd_socket, buf, strlen(user)+strlen(passwd)+2);
      len = read(fd_socket, buf, sizeof(buf));
      if (len >= 0)
      {
        buf[len] = 0;
      }
      DBG(DBG_info2, "< %s", buf);
     break;

    case EMAIL_AUTH_ASMTP_LOGIN:
      snprintf(buf, sizeof(buf), "AUTH LOGIN\r\n");
      DBG(DBG_info2, "> %s", buf);
      write(fd_socket, buf, strlen(buf));
      len = read(fd_socket, buf, sizeof(buf));
      if (len >= 0)
      {
        buf[len] = 0;
      }
      DBG(DBG_info2, "< %s", buf);
      if (buf[0] != '3')
      {
        DBG(DBG_info, "=> error\n");
       return (-1);
      }

      DBG(DBG_info2, "> (USERNAME)\n");
      write_string_base64(fd_socket, user, strlen(user));

      len = read(fd_socket, buf, sizeof(buf));
      if (len >= 0)
      {
        buf[len] = 0;
      }
      DBG(DBG_info2, "< %s", buf);
      if (buf[0] != '3')
      {
        DBG(DBG_info, "=> error\n");
       return (-1);
      }

      DBG(DBG_info2, "> (PASSWORD)\n");
      write_string_base64(fd_socket, passwd, strlen(passwd));

      len = read(fd_socket, buf, sizeof(buf));
      if (len >= 0)
      {
        buf[len] = 0;
      }
      DBG(DBG_info2, "< %s", buf);
      if (buf[0] != '2')
      {
        DBG(DBG_info, "=> error\n");
       return (-1);
      }
     break;

#if 0
    case EMAIL_AUTH_ASMTP_CRAM_MD5:
      snprintf(buf, sizeof(buf), "AUTH CRAM-MD5\r\n");
      DBG(DBG_info2, "> %s", buf);
      write(fd_socket, buf, strlen(buf));
      len = read(fd_socket, buf, sizeof(buf));
      if (len >= 0)
      {
        buf[len] = 0;
      }
      DBG(DBG_info2, "< %s", buf);
     break;
#endif

    default:
       DBG(DBG_proc, "no valid asmtp authentication type\n");
     break;
  }

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* not only a write routine, also reads data */
/* returns -1 on error, 0 when ok */
int write_smtp_header(int fd_socket, char *from, char *to, int auth_type, char *user, char *passwd)
{
 char buf[1024];
 int len;

  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  if (auth_type < EMAIL_AUTH_ASMTP_PLAIN)
  {
    snprintf(buf, sizeof(buf), "HELO localhost\r\n");
  }
  else
  {
    snprintf(buf, sizeof(buf), "EHLO localhost\r\n");
  }
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  if (buf[0] != '2')
  {
    DBG(DBG_info, "=> error\n");

    if (xsane.email_status)
    {
      free(xsane.email_status);
    }
    xsane.email_status = strdup(TEXT_EMAIL_STATUS_SMTP_CONNECTION_FAILED);
    xsane_front_gtk_email_project_update_lockfile_status();
   return -1;
  }

  if (asmtp_authentication(fd_socket, auth_type, user, passwd))
  {
    xsane.email_status = strdup(TEXT_EMAIL_STATUS_ASMTP_AUTH_FAILED);
    xsane_front_gtk_email_project_update_lockfile_status();
   return -1;
  }

  snprintf(buf, sizeof(buf), "MAIL FROM: %s\r\n", from);
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  if (buf[0] != '2')
  {
    DBG(DBG_info, "=> error\n");

    if (xsane.email_status)
    {
      free(xsane.email_status);
    }
    xsane.email_status = strdup(TEXT_EMAIL_STATUS_SMTP_ERR_FROM);
    xsane_front_gtk_email_project_update_lockfile_status();
   return -1;
  }
  

  snprintf(buf, sizeof(buf), "RCPT TO: %s\r\n", to);
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  if (buf[0] != '2')
  {
    DBG(DBG_info, "=> error\n");

    if (xsane.email_status)
    {
      free(xsane.email_status);
    }
    xsane.email_status = strdup(TEXT_EMAIL_STATUS_SMTP_ERR_RCPT);
    xsane_front_gtk_email_project_update_lockfile_status();
   return -1;
  }

  snprintf(buf, sizeof(buf), "DATA\r\n");
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  if ((buf[0] != '2') && (buf[0] != '3'))
  {
    DBG(DBG_info, "=> error\n");

    if (xsane.email_status)
    {
      free(xsane.email_status);
    }
    xsane.email_status = strdup(TEXT_EMAIL_STATUS_SMTP_ERR_DATA);
    xsane_front_gtk_email_project_update_lockfile_status();
   return -1;
  }

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

/* not only a write routine, also reads data */
int write_smtp_footer(int fd_socket)
{
 char buf[1024];
 int len;

  snprintf(buf, sizeof(buf), "\r\n.\r\n");
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

  snprintf(buf, sizeof(buf), "QUIT\r\n");
  DBG(DBG_info2, "> %s", buf);
  write(fd_socket, buf, strlen(buf));
  len = read(fd_socket, buf, sizeof(buf));
  if (len >= 0)
  {
    buf[len] = 0;
  }
  DBG(DBG_info2, "< %s\n", buf);

 return 0;
}

#endif

/* ---------------------------------------------------------------------------------------------------------------------- */
