/* glpimg1.c */

/*----------------------------------------------------------------------
-- Copyright (C) 2000, 2001, 2002 Andrew Makhorin <mao@mai2.rcnet.ru>,
--               Department for Applied Informatics, Moscow Aviation
--               Institute, Moscow, Russia. All rights reserved.
--
-- This file is a part of GLPK (GNU Linear Programming Kit).
--
-- GLPK 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, or (at your option)
-- any later version.
--
-- GLPK 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 GLPK; see the file COPYING. If not, write to the Free
-- Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
----------------------------------------------------------------------*/

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "glpimg.h"
#include "glplib.h"

#define error print

/*----------------------------------------------------------------------
-- create_img - create raster image.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- IMG *create_img(int type, int xsize, int ysize);
--
-- *Description*
--
-- The create_img routine creates a raster image.
--
-- The parameter type specifies the image type:
--
-- IMG_2    2 colors (1 bit per pixel);
-- IMG_4    4 colors (2 bits per pixel);
-- IMG_16   16 colors (4 bits per pixel);
-- IMG_256  256 colors (8 bits per pixel).
--
-- The parameters xsize and ysize specify respectively width and height
-- of the image (in pixels).
--
-- *Returns*
--
-- The create_img returns a pointer to the created image. */

int (*save_img)(IMG *img, char *fname) = save_as_bmp;

IMG *create_img(int type, int xsize, int ysize)
{     IMG *img;
      int depth;
      insist(CHAR_BIT == 8);
      if (!(type == IMG_2 || type == IMG_4 || type == IMG_16 ||
            type == IMG_256))
         fault("create_img: invalid image type");
      if (!(1 <= xsize && xsize <= 16000))
         fault("create_img: invalid image width");
      if (!(1 <= ysize && ysize <= 16000))
         fault("create_img: invalid image height");
      img = umalloc(sizeof(IMG));
      img->type = type;
      img->xres = img->yres = 2835; /* 72 pixels per inch (25.4 mm) */
      img->xsize = xsize;
      img->ysize = ysize;
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            depth = 2;
            img->len = (img->xsize + 7) / 8;
            img->color = 1;
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            depth = 4;
            img->len = (2 * img->xsize + 7) / 8;
            img->color = 3;
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            depth = 16;
            img->len = (4 * img->xsize + 7) / 8;
            img->color = 15;
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            depth = 256;
            img->len = img->xsize;
            img->color = 15;
            break;
         default:
            insist(img->type != img->type);
      }
      img->rgb = ucalloc(depth, sizeof(RGB));
      reset_rgb(img);
      img->scan = ucalloc(img->ysize, img->len);
      memset(img->scan, 0, img->ysize * img->len);
      img->x0 = img->y0 = 0;
      img->x1 = img->y1 = 0;
      img->x2 = xsize - 1;
      img->y2 = ysize - 1;
      img->x = img->y = 0;
      return img;
}

/*----------------------------------------------------------------------
-- reset_rgb - initialize color map table by default spectrum.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void reset_rgb(IMG *img);
--
-- *Description*
--
-- The reset_rgb routine sets all entries of the color map table for
-- the raster image to default standard values.
--
-- Normally, the video BIOS programs the video DAC registers with a
-- default spectrum of color values. Registers 00H through 0FH contain
-- the default gamut of CGA-compatible colors. Registers 10H through
-- 1FH contain a gray scale of gradually increasing intensity. The next
-- 216 registers (20H through F7H) contain three groups of 72 colors,
-- with the first group (registers 20H through 67H) at high intensity,
-- the second (registers 68H through AFH) at an intermediate intensity,
-- and the third (registers B0H through F7H) at low intensity. Each
-- 72-color group is made up of three ranges of colors of decreasing
-- saturation (increasing whiteness); each range varies smoothly in hue
-- from blue to red to green. */

static RGB dac[256] =
{     /* 00-0F: CGA-compatible default colors */
      { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0xA8 },
      { 0x00, 0xA8, 0x00 }, { 0x00, 0xA8, 0xA8 },
      { 0xA8, 0x00, 0x00 }, { 0xA8, 0x00, 0xA8 },
      { 0xA8, 0x54, 0x00 }, { 0xA8, 0xA8, 0xA8 },
      { 0x54, 0x54, 0x54 }, { 0x54, 0x54, 0xFF },
      { 0x54, 0xFF, 0x54 }, { 0x54, 0xFF, 0xFF },
      { 0xFF, 0x54, 0x54 }, { 0xFF, 0x54, 0xFF },
      { 0xFF, 0xFF, 0x54 }, { 0xFF, 0xFF, 0xFF },
      /* 10-1F: gray scale */
      { 0x00, 0x00, 0x00 }, { 0x14, 0x14, 0x14 },
      { 0x20, 0x20, 0x20 }, { 0x2C, 0x2C, 0x2C },
      { 0x38, 0x38, 0x38 }, { 0x44, 0x44, 0x44 },
      { 0x50, 0x50, 0x50 }, { 0x60, 0x60, 0x60 },
      { 0x70, 0x70, 0x70 }, { 0x80, 0x80, 0x80 },
      { 0x90, 0x90, 0x90 }, { 0xA0, 0xA0, 0xA0 },
      { 0xB4, 0xB4, 0xB4 }, { 0xC8, 0xC8, 0xC8 },
      { 0xE0, 0xE0, 0xE0 }, { 0xFF, 0xFF, 0xFF },
      /* 20-37: b-r-g-b (high intensity, high saturation) */
      { 0x00, 0x00, 0xFF }, { 0x40, 0x00, 0xFF },
      { 0x7C, 0x00, 0xFF }, { 0xBC, 0x00, 0xFF },
      { 0xFF, 0x00, 0xFF }, { 0xFF, 0x00, 0xBC },
      { 0xFF, 0x00, 0x7C }, { 0xFF, 0x00, 0x40 },
      { 0xFF, 0x00, 0x00 }, { 0xFF, 0x40, 0x00 },
      { 0xFF, 0x7C, 0x00 }, { 0xFF, 0xBC, 0x00 },
      { 0xFF, 0xFF, 0x00 }, { 0xBC, 0xFF, 0x00 },
      { 0x7C, 0xFF, 0x00 }, { 0x40, 0xFF, 0x00 },
      { 0x00, 0xFF, 0x00 }, { 0x00, 0xFF, 0x40 },
      { 0x00, 0xFF, 0x7C }, { 0x00, 0xFF, 0xBC },
      { 0x00, 0xFF, 0xFF }, { 0x00, 0xBC, 0xFF },
      { 0x00, 0x7C, 0xFF }, { 0x00, 0x40, 0xFF },
      /* 38-4F: b-r-g-b (high intensity, moderate saturation) */
      { 0x7C, 0x7C, 0xFF }, { 0x9C, 0x7C, 0xFF },
      { 0xBC, 0x7C, 0xFF }, { 0xDC, 0x7C, 0xFF },
      { 0xFF, 0x7C, 0xFF }, { 0xFF, 0x7C, 0xDC },
      { 0xFF, 0x7C, 0xBC }, { 0xFF, 0x7C, 0x9C },
      { 0xFF, 0x7C, 0x7C }, { 0xFF, 0x9C, 0x7C },
      { 0xFF, 0xBC, 0x7C }, { 0xFF, 0xDC, 0x7C },
      { 0xFF, 0xFF, 0x7C }, { 0xDC, 0xFF, 0x7C },
      { 0xBC, 0xFF, 0x7C }, { 0x9C, 0xFF, 0x7C },
      { 0x7C, 0xFF, 0x7C }, { 0x7C, 0xFF, 0x9C },
      { 0x7C, 0xFF, 0xBC }, { 0x7C, 0xFF, 0xDC },
      { 0x7C, 0xFF, 0xFF }, { 0x7C, 0xDC, 0xFF },
      { 0x7C, 0xBC, 0xFF }, { 0x7C, 0x9C, 0xFF },
      /* 50-67: b-r-g-b (high intensity, low saturation) */
      { 0xB4, 0xB4, 0xFF }, { 0xC4, 0xB4, 0xFF },
      { 0xD8, 0xB4, 0xFF }, { 0xE8, 0xB4, 0xFF },
      { 0xFF, 0xB4, 0xFF }, { 0xFF, 0xB4, 0xE8 },
      { 0xFF, 0xB4, 0xD8 }, { 0xFF, 0xB4, 0xC4 },
      { 0xFF, 0xB4, 0xB4 }, { 0xFF, 0xC4, 0xB4 },
      { 0xFF, 0xD8, 0xB4 }, { 0xFF, 0xE8, 0xB4 },
      { 0xFF, 0xFF, 0xB4 }, { 0xE8, 0xFF, 0xB4 },
      { 0xD8, 0xFF, 0xB4 }, { 0xC4, 0xFF, 0xB4 },
      { 0xB4, 0xFF, 0xB4 }, { 0xB4, 0xFF, 0xC4 },
      { 0xB4, 0xFF, 0xD8 }, { 0xB4, 0xFF, 0xE8 },
      { 0xB4, 0xFF, 0xFF }, { 0xB4, 0xE8, 0xFF },
      { 0xB4, 0xD8, 0xFF }, { 0xB4, 0xC4, 0xFF },
      /* 68-7F: b-r-g-b (moderate intensity, high saturation) */
      { 0x00, 0x00, 0x70 }, { 0x1C, 0x00, 0x70 },
      { 0x38, 0x00, 0x70 }, { 0x54, 0x00, 0x70 },
      { 0x70, 0x00, 0x70 }, { 0x70, 0x00, 0x54 },
      { 0x70, 0x00, 0x38 }, { 0x70, 0x00, 0x1C },
      { 0x70, 0x00, 0x00 }, { 0x70, 0x1C, 0x00 },
      { 0x70, 0x38, 0x00 }, { 0x70, 0x54, 0x00 },
      { 0x70, 0x70, 0x00 }, { 0x54, 0x70, 0x00 },
      { 0x38, 0x70, 0x00 }, { 0x1C, 0x70, 0x00 },
      { 0x00, 0x70, 0x00 }, { 0x00, 0x70, 0x1C },
      { 0x00, 0x70, 0x38 }, { 0x00, 0x70, 0x54 },
      { 0x00, 0x70, 0x70 }, { 0x00, 0x54, 0x70 },
      { 0x00, 0x38, 0x70 }, { 0x00, 0x1C, 0x70 },
      /* 80-97: b-r-g-b (moderate intensity, moderate saturation) */
      { 0x38, 0x38, 0x70 }, { 0x44, 0x38, 0x70 },
      { 0x54, 0x38, 0x70 }, { 0x60, 0x38, 0x70 },
      { 0x70, 0x38, 0x70 }, { 0x70, 0x38, 0x60 },
      { 0x70, 0x38, 0x54 }, { 0x70, 0x38, 0x44 },
      { 0x70, 0x38, 0x38 }, { 0x70, 0x44, 0x38 },
      { 0x70, 0x54, 0x38 }, { 0x70, 0x60, 0x38 },
      { 0x70, 0x70, 0x38 }, { 0x60, 0x70, 0x38 },
      { 0x54, 0x70, 0x38 }, { 0x44, 0x70, 0x38 },
      { 0x38, 0x70, 0x38 }, { 0x38, 0x70, 0x44 },
      { 0x38, 0x70, 0x54 }, { 0x38, 0x70, 0x60 },
      { 0x38, 0x70, 0x70 }, { 0x38, 0x60, 0x70 },
      { 0x38, 0x54, 0x70 }, { 0x38, 0x44, 0x70 },
      /* 98-AF: b-r-g-b (moderate intensity, low saturation) */
      { 0x50, 0x50, 0x70 }, { 0x58, 0x50, 0x70 },
      { 0x60, 0x50, 0x70 }, { 0x68, 0x50, 0x70 },
      { 0x70, 0x50, 0x70 }, { 0x70, 0x50, 0x68 },
      { 0x70, 0x50, 0x60 }, { 0x70, 0x50, 0x58 },
      { 0x70, 0x50, 0x50 }, { 0x70, 0x58, 0x50 },
      { 0x70, 0x60, 0x50 }, { 0x70, 0x68, 0x50 },
      { 0x70, 0x70, 0x50 }, { 0x68, 0x70, 0x50 },
      { 0x60, 0x70, 0x50 }, { 0x58, 0x70, 0x50 },
      { 0x50, 0x70, 0x50 }, { 0x50, 0x70, 0x58 },
      { 0x50, 0x70, 0x60 }, { 0x50, 0x70, 0x68 },
      { 0x50, 0x70, 0x70 }, { 0x50, 0x68, 0x70 },
      { 0x50, 0x60, 0x70 }, { 0x50, 0x58, 0x70 },
      /* B0-C7: b-r-g-b (low intensity, high saturation) */
      { 0x00, 0x00, 0x40 }, { 0x10, 0x00, 0x40 },
      { 0x20, 0x00, 0x40 }, { 0x30, 0x00, 0x40 },
      { 0x40, 0x00, 0x40 }, { 0x40, 0x00, 0x30 },
      { 0x40, 0x00, 0x20 }, { 0x40, 0x00, 0x10 },
      { 0x40, 0x00, 0x00 }, { 0x40, 0x10, 0x00 },
      { 0x40, 0x20, 0x00 }, { 0x40, 0x30, 0x00 },
      { 0x40, 0x40, 0x00 }, { 0x30, 0x40, 0x00 },
      { 0x20, 0x40, 0x00 }, { 0x10, 0x40, 0x00 },
      { 0x00, 0x40, 0x00 }, { 0x00, 0x40, 0x10 },
      { 0x00, 0x40, 0x20 }, { 0x00, 0x40, 0x30 },
      { 0x00, 0x40, 0x40 }, { 0x00, 0x30, 0x40 },
      { 0x00, 0x20, 0x40 }, { 0x00, 0x10, 0x40 },
      /* C8-DF: b-r-g-b (low intensity, moderate saturation) */
      { 0x20, 0x20, 0x40 }, { 0x28, 0x20, 0x40 },
      { 0x30, 0x20, 0x40 }, { 0x38, 0x20, 0x40 },
      { 0x40, 0x20, 0x40 }, { 0x40, 0x20, 0x38 },
      { 0x40, 0x20, 0x30 }, { 0x40, 0x20, 0x28 },
      { 0x40, 0x20, 0x20 }, { 0x40, 0x28, 0x20 },
      { 0x40, 0x30, 0x20 }, { 0x40, 0x38, 0x20 },
      { 0x40, 0x40, 0x20 }, { 0x38, 0x40, 0x20 },
      { 0x30, 0x40, 0x20 }, { 0x28, 0x40, 0x20 },
      { 0x20, 0x40, 0x20 }, { 0x20, 0x40, 0x28 },
      { 0x20, 0x40, 0x30 }, { 0x20, 0x40, 0x38 },
      { 0x20, 0x40, 0x40 }, { 0x20, 0x38, 0x40 },
      { 0x20, 0x30, 0x40 }, { 0x20, 0x28, 0x40 },
      /* E0-F7: b-r-g-b (low intensity, low saturation) */
      { 0x2C, 0x2C, 0x40 }, { 0x30, 0x2C, 0x40 },
      { 0x34, 0x2C, 0x40 }, { 0x3C, 0x2C, 0x40 },
      { 0x40, 0x2C, 0x40 }, { 0x40, 0x2C, 0x3C },
      { 0x40, 0x2C, 0x34 }, { 0x40, 0x2C, 0x30 },
      { 0x40, 0x2C, 0x2C }, { 0x40, 0x30, 0x2C },
      { 0x40, 0x34, 0x2C }, { 0x40, 0x3C, 0x2C },
      { 0x40, 0x40, 0x2C }, { 0x3C, 0x40, 0x2C },
      { 0x34, 0x40, 0x2C }, { 0x30, 0x40, 0x2C },
      { 0x2C, 0x40, 0x2C }, { 0x2C, 0x40, 0x30 },
      { 0x2C, 0x40, 0x34 }, { 0x2C, 0x40, 0x3C },
      { 0x2C, 0x40, 0x40 }, { 0x2C, 0x3C, 0x40 },
      { 0x2C, 0x34, 0x40 }, { 0x2C, 0x30, 0x40 },
      /* F8-FF: black */
      { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 },
      { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 },
      { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 },
      { 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00 },
};

void reset_rgb(IMG *img)
{     int c;
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            img->rgb[0] = dac[0x00]; /* black */
            img->rgb[1] = dac[0x0F]; /* white */
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            img->rgb[0] = dac[0x00]; /* black */
            img->rgb[1] = dac[0x0B]; /* cyan */
            img->rgb[2] = dac[0x0D]; /* magenta */
            img->rgb[3] = dac[0x0F]; /* white */
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            for (c = 0x00; c <= 0x0F; c++) img->rgb[c] = dac[c];
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            for (c = 0x00; c <= 0xFF; c++) img->rgb[c] = dac[c];
            break;
         default:
            insist(img->type != img->type);
      }
      return;
}

/*----------------------------------------------------------------------
-- set_imgval.c - set pixel to given value.
--
-- *Synopsis*
--
-- #include "glpmat.h"
-- void set_imgval(IMG *img, int x, int y, int val);
--
-- *Description*
--
-- The set_imgval routine sets the pixel of the raster image which has
-- absolute coordinates (x,y) to the given value val.
--
-- If the pixel (x,y) is out of the image, the routine does nothing. */

void set_imgval(IMG *img, int x, int y, int val)
{     unsigned char *byte;
      if (!(0 <= x && x < img->xsize && 0 <= y && y < img->ysize))
         goto skip;
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            byte = img->scan + img->len * y + (x >> 3);
            switch (x & 7)
            {  case 0:
                  *byte &= 0x7F, *byte |= (char)((val & 1) << 7);
                  break;
               case 1:
                  *byte &= 0xBF, *byte |= (char)((val & 1) << 6);
                  break;
               case 2:
                  *byte &= 0xDF, *byte |= (char)((val & 1) << 5);
                  break;
                case 3:
                  *byte &= 0xEF, *byte |= (char)((val & 1) << 4);
                  break;
                case 4:
                  *byte &= 0xF7, *byte |= (char)((val & 1) << 3);
                  break;
                case 5:
                  *byte &= 0xFB, *byte |= (char)((val & 1) << 2);
                  break;
                case 6:
                  *byte &= 0xFD, *byte |= (char)((val & 1) << 1);
                  break;
                case 7:
                  *byte &= 0xFE, *byte |= (char)((val & 1) << 0);
                  break;
            }
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            byte = img->scan + img->len * y + (x >> 2);
            switch (x & 3)
            {  case 0:
                  *byte &= 0x3F, *byte |= (char)((val & 3) << 6);
                  break;
               case 1:
                  *byte &= 0xCF, *byte |= (char)((val & 3) << 4);
                  break;
               case 2:
                  *byte &= 0xF3, *byte |= (char)((val & 3) << 2);
                  break;
               case 3:
                  *byte &= 0xFC, *byte |= (char)((val & 3) << 0);
                  break;
            }
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            byte = img->scan + img->len * y + (x >> 1);
            switch (x & 1)
            {  case 0:
                  *byte &= 0x0F, *byte |= (char)((val & 15) << 4);
                  break;
               case 1:
                  *byte &= 0xF0, *byte |= (char)((val & 15) << 0);
                  break;
            }
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            byte = img->scan + img->len * y + x;
            *byte = (char)(val & 255);
            break;
         default:
            insist(img->type != img->type);
      }
skip: return;
}

/*----------------------------------------------------------------------
-- get_imgval - get current value of pixel.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- int get_imgval(IMG *img, int x, int y);
--
-- *Returns*
--
-- The get_imgval routine returns the current value of the pixel of the
-- raster image which has absolute coordinates (x,y). If the pixel (x,y)
-- is out of the image, the routine returns negative value. */

int get_imgval(IMG *img, int x, int y)
{     unsigned char *scan;
      int val = -1;
      if (!(0 <= x && x < img->xsize && 0 <= y && y < img->ysize))
         goto skip;
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            scan = img->scan + img->len * y + (x >> 3);
            switch (x & 7)
            {  case 0: val = (*scan >> 7) & 1; break;
               case 1: val = (*scan >> 6) & 1; break;
               case 2: val = (*scan >> 5) & 1; break;
               case 3: val = (*scan >> 4) & 1; break;
               case 4: val = (*scan >> 3) & 1; break;
               case 5: val = (*scan >> 2) & 1; break;
               case 6: val = (*scan >> 1) & 1; break;
               case 7: val = (*scan >> 0) & 1; break;
            }
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            scan = img->scan + img->len * y + (x >> 2);
            switch (x & 3)
            {  case 0: val = (*scan >> 6) & 3; break;
               case 1: val = (*scan >> 4) & 3; break;
               case 2: val = (*scan >> 2) & 3; break;
               case 3: val = (*scan >> 0) & 3; break;
            }
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            scan = img->scan + img->len * y + (x >> 1);
            switch (x & 1)
            {  case 0: val = (*scan >> 4) & 15; break;
               case 1: val = (*scan >> 0) & 15; break;
            }
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            scan = img->scan + img->len * y + x;
            val = *scan & 255;
            break;
         default:
            insist(img->type != img->type);
      }
skip: return val;
}

/*----------------------------------------------------------------------
-- delete_img - delete raster image.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void delete_img(IMG *img);
--
-- *Description*
--
-- The delete_img routine deletes raster image freeing all the memory
-- allocated to this object. */

void delete_img(IMG *img)
{     ufree(img->rgb);
      ufree(img->scan);
      ufree(img);
      return;
}

/*----------------------------------------------------------------------
-- save_as_bmp - write raster image using Windows Bitmap format.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- int save_as_bmp(IMG *img, char *fname);
--
-- *Description*
--
-- The save_as_bmp routine writes the raster image using uncompressed
-- Windows Bitmap format to the binary file whose name is the character
-- string fname.
--
-- *Returns*
--
-- If the operation completed successfully, the routine returns zero.
-- Otherwise the routine returns non-zero. */

static void put_byte(FILE *fp, int val)
{     unsigned char b = (unsigned char)val;
      fwrite(&b, sizeof(char), 1, fp);
      return;
}

static void put_word(FILE *fp, int val) /* big endian */
{     put_byte(fp, val);
      put_byte(fp, val >> 8);
      return;
}

static void put_dword(FILE *fp, int val) /* big endian */
{     put_word(fp, val);
      put_word(fp, val >> 16);
      return;
}

int save_as_bmp(IMG *img, char *fname)
{     FILE *fp;
      int offset, bmsize, bitcnt;
      fp = fopen(fname, "wb");
      if (fp == NULL)
      {  error("save_as_bmp: can't create `%s' - %s", fname,
            strerror(errno));
         goto fail;
      }
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            offset = 14 + 40 + 2 * 4;
            bmsize = (1 * img->xsize + 31) / 32;
            bitcnt = 1;
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
         case IMG_16: /* 16 colors (4 bits per pixel) */
            offset = 14 + 40 + 16 * 4;
            bmsize = (4 * img->xsize + 31) / 32;
            bitcnt = 4;
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            offset = 14 + 40 + 256 * 4;
            bmsize = (8 * img->xsize + 31) / 32;
            bitcnt = 8;
            break;
         default:
            insist(img->type != img->type);
      }
      /* struct BMPFILEHEADER */
      /* UINT bfType */
      put_byte(fp, 0x42);
      put_byte(fp, 0x4D);
      /* DWORD bfSize */
      put_dword(fp, offset + bmsize * 4);
      /* UINT bfReserved1 */
      put_word(fp, 0);
      /* UNIT bfReserved2 */
      put_word(fp, 0);
      /* DWORD bfOffBits */
      put_dword(fp, offset);
      /* struct BMPINFOHEADER */
      /* DWORD biSize */
      put_dword(fp, 40);
      /* LONG biWidth */
      put_dword(fp, img->xsize);
      /* LONG biHeight */
      put_dword(fp, img->ysize);
      /* WORD biPlanes */
      put_word(fp, 1);
      /* WORD biBitCount */
      put_word(fp, bitcnt);
      /* DWORD biCompression */
      put_dword(fp, 0 /* BI_RGB (not compressed) */);
      /* DWORD biSizeImage */
      put_dword(fp, 0);
      /* LONG biXPelsPerMeter */
      put_dword(fp, img->xres);
      /* LONG biYPelsPerMeter */
      put_dword(fp, img->yres);
      /* DWORD biClrUsed */
      put_dword(fp, 0);
      /* DWORD biClrImportant */
      put_dword(fp, 0);
      /* struct RGBQUAD and pixel data bits */
      switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            {  int k;
               for (k = 0; k < 2; k++)
               {  put_byte(fp, img->rgb[k].blue);
                  put_byte(fp, img->rgb[k].green);
                  put_byte(fp, img->rgb[k].red);
                  put_byte(fp, 0);
               }
               for (k = img->ysize - 1; k >= 0; k--)
               {  fwrite(img->scan + img->len * k, img->len, 1, fp);
                  fwrite("???", (- img->len) & 3, 1, fp);
               }
            }
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            {  int k, t;
               unsigned char *byte, val[2];
               for (k = 0; k < 16; k++)
               {  put_byte(fp, img->rgb[k & 3].blue);
                  put_byte(fp, img->rgb[k & 3].green);
                  put_byte(fp, img->rgb[k & 3].red);
                  put_byte(fp, 0);
               }
               for (k = img->ysize - 1; k >= 0; k--)
               {  byte = img->scan + img->len * k;
                  for (t = 0; t < img->len; t++, byte++)
                  {  val[0] = (unsigned char)
                        (((*byte >> 2) & 0x30) | ((*byte >> 4) & 0x03));
                     val[1] = (unsigned char)
                        (((*byte << 2) & 0x30) | ((*byte << 0) & 0x03));
                     put_byte(fp, val[0]);
                     put_byte(fp, val[1]);
                  }
                  fwrite("???", (- 2 * img->len) & 3, 1, fp);
               }
            }
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            {  int k;
               for (k = 0; k < 16; k++)
               {  put_byte(fp, img->rgb[k].blue);
                  put_byte(fp, img->rgb[k].green);
                  put_byte(fp, img->rgb[k].red);
                  put_byte(fp, 0);
               }
               for (k = img->ysize - 1; k >= 0; k--)
               {  fwrite(img->scan + img->len * k, img->len, 1, fp);
                  fwrite("???", (- img->len) & 3, 1, fp);
               }
            }
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            {  int k;
               for (k = 0; k < 256; k++)
               {  put_byte(fp, img->rgb[k].blue);
                  put_byte(fp, img->rgb[k].green);
                  put_byte(fp, img->rgb[k].red);
                  put_byte(fp, 0);
               }
               for (k = img->ysize - 1; k >= 0; k--)
               {  fwrite(img->scan + img->len * k, img->len, 1, fp);
                  fwrite("???", (- img->len) & 3, 1, fp);
               }
            }
            break;
         default:
            insist(img->type != img->type);
      }
      fflush(fp);
      if (ferror(fp))
      {  error("save_as_bmp: can't write to `%s' - %s", fname,
            strerror(errno));
         goto fail;
      }
      fclose(fp);
      return 0;
fail: if (fp != NULL) fclose(fp);
      return 1;
}

/*----------------------------------------------------------------------
-- save_as_tiff - write raster image using TIFF format.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- int save_as_tiff(IMG *img, char *fname);
--
-- *Description*
--
-- The save_as_tiff routine writes the raster image using uncompressed
-- TIFF format to the binary file, whose name is the character string
-- fname.
--
-- *Returns*
--
-- If the operation completed successfully, the routine returns zero.
-- Otherwise the routine returns non-zero. */

#define put_byte put_byte1

static void put_byte(FILE *fp, int val)
{     unsigned char b = (unsigned char)val;
      fwrite(&b, sizeof(char), 1, fp);
      return;
}

#define put_word put_word1

static void put_word(FILE *fp, int val) /* big endian */
{     put_byte(fp, val);
      put_byte(fp, val >> 8);
      return;
}

#define put_dword put_dword1

static void put_dword(FILE *fp, int val) /* big endian */
{     put_word(fp, val);
      put_word(fp, val >> 16);
      return;
}

int save_as_tiff(IMG *img, char *fname)
{     FILE *fp;
      int loc_0111, loc_0140, loc_011A, loc_011B, k, x, y, temp;
      fp = fopen(fname, "wb");
      if (fp == NULL)
      {  error("save_as_tiff: can't create `%s' - %s", fname,
            strerror(errno));
         goto fail;
      }
      /* 8-byte image file header */
      /* byte ordering used within the file = 0x4949 (big endian) */
      put_byte(fp, 0x49);
      put_byte(fp, 0x49);
      /* TIFF version number = 42 = 0x2A */
      put_word(fp, 0x2A);
      /* offset (in bytes) of the first (and the only) Image File
         Directory (IFD); it follows the image file header */
      put_dword(fp, 8);
      /*** IFD starts here ***/
      /* number of 12-byte IFD entries */
      put_word(fp, 17);
      /* subfile type */
      put_word(fp, 0x00FE);
      put_word(fp, 4 /* long */);
      put_dword(fp, 1);
      put_dword(fp, 0);
      /* image width (in pixels) */
      put_word(fp, 0x0100);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, img->xsize);
      /* image heigth (in pixels) */
      put_word(fp, 0x0101);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, img->ysize);
      /* bits per sample */
      put_word(fp, 0x0102);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      switch (img->type)
      {  case IMG_2:    put_dword(fp, 1); break;
         case IMG_4:    put_dword(fp, 4); break;
         case IMG_16:   put_dword(fp, 4); break;
         case IMG_256:  put_dword(fp, 8); break;
         default: insist(img->type != img->type);
      }
      /* compression */
      put_word(fp, 0x0103);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 1 /* no compression */);
      /* photometric interpretation */
      put_word(fp, 0x0106);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 3 /* color palette used */);
      /* fill order */
      put_word(fp, 0x010A);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 1 /* most significant fill first */);
      /* strip offset */
      put_word(fp, 0x0111);
      put_word(fp, 4 /* long */);
      put_dword(fp, 1);
      loc_0111 = ftell(fp);
      put_dword(fp, 0 /* will be stored later */);
      /* orientation */
      put_word(fp, 0x0112);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 1 /* left upper corner */);
      /* sample per pixel */
      put_word(fp, 0x0115);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 1 /* if color palette used */);
      /* rows per strip */
      put_word(fp, 0x0116);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, img->ysize);
      /* strip byte counts */
      put_word(fp, 0x0117);
      put_word(fp, 4 /* long */);
      put_dword(fp, 1);
      switch (img->type)
      {  case IMG_2:
            put_dword(fp, ((img->xsize + 7) / 8) * img->ysize);
            break;
         case IMG_4:
            put_dword(fp, ((img->xsize + 1) / 2) * img->ysize);
            break;
         case IMG_16:
            put_dword(fp, ((img->xsize + 1) / 2) * img->ysize);
            break;
         case IMG_256:
            put_dword(fp, img->xsize * img->ysize);
            break;
         default:
            insist(img->type != img->type);
      }
      /* horizontal resolution */
      put_word(fp, 0x011A);
      put_word(fp, 5 /* ratio */);
      put_dword(fp, 1);
      loc_011A = ftell(fp);
      put_dword(fp, 0 /* will be stored later */);
      /* vertical resolution */
      put_word(fp, 0x011B);
      put_word(fp, 5 /* ratio */);
      put_dword(fp, 1);
      loc_011B = ftell(fp);
      put_dword(fp, 0 /* will be stored later */);
      /* planar configuration */
      put_word(fp, 0x011C);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 1 /* contiguous */);
      /* resolution unit */
      put_word(fp, 0x0128);
      put_word(fp, 3 /* short */);
      put_dword(fp, 1);
      put_dword(fp, 3 /* centimeter */);
      /* color map */
      put_word(fp, 0x0140);
      put_word(fp, 3 /* short */);
      switch (img->type)
      {  case IMG_2:    put_dword(fp, 3 * 2);   break;
         case IMG_4:    put_dword(fp, 3 * 16);  break;
         case IMG_16:   put_dword(fp, 3 * 16);  break;
         case IMG_256:  put_dword(fp, 3 * 256); break;
         default: insist(img->type != img->type);
      }
      loc_0140 = ftell(fp);
      put_dword(fp, 0 /* will be stored later */);
      /*** end of IFD ***/
      /* align on dword boundary */
      while (ftell(fp) % 4) put_byte(fp, '?');
      /* x resolution */
      temp = ftell(fp);
      fseek(fp, loc_011A, SEEK_SET);
      put_dword(fp, temp);
      fseek(fp, 0, SEEK_END);
      put_dword(fp, img->xres);
      put_dword(fp, 100);
      /* y resolution */
      temp = ftell(fp);
      fseek(fp, loc_011B, SEEK_SET);
      put_dword(fp, temp);
      fseek(fp, 0, SEEK_END);
      put_dword(fp, img->yres);
      put_dword(fp, 100);
      /* color map */
      temp = ftell(fp);
      fseek(fp, loc_0140, SEEK_SET);
      put_dword(fp, temp);
      fseek(fp, 0, SEEK_END);
      switch (img->type)
      {  case IMG_2:
            for (k = 0; k < 2; k++)
            {  put_byte(fp, img->rgb[k].red);
               put_byte(fp, img->rgb[k].red);
            }
            for (k = 0; k < 2; k++)
            {  put_byte(fp, img->rgb[k].green);
               put_byte(fp, img->rgb[k].green);
            }
            for (k = 0; k < 2; k++)
            {  put_byte(fp, img->rgb[k].blue);
               put_byte(fp, img->rgb[k].blue);
            }
            break;
         case IMG_4:
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k & 3].red);
               put_byte(fp, img->rgb[k & 3].red);
            }
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k & 3].green);
               put_byte(fp, img->rgb[k & 3].green);
            }
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k & 3].blue);
               put_byte(fp, img->rgb[k & 3].blue);
            }
            break;
         case IMG_16:
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k].red);
               put_byte(fp, img->rgb[k].red);
            }
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k].green);
               put_byte(fp, img->rgb[k].green);
            }
            for (k = 0; k < 16; k++)
            {  put_byte(fp, img->rgb[k].blue);
               put_byte(fp, img->rgb[k].blue);
            }
            break;
         case IMG_256:
            for (k = 0; k < 256; k++)
            {  put_byte(fp, img->rgb[k].red);
               put_byte(fp, img->rgb[k].red);
            }
            for (k = 0; k < 256; k++)
            {  put_byte(fp, img->rgb[k].green);
               put_byte(fp, img->rgb[k].green);
            }
            for (k = 0; k < 256; k++)
            {  put_byte(fp, img->rgb[k].blue);
               put_byte(fp, img->rgb[k].blue);
            }
            break;
         default:
            insist(img->type != img->type);
      }
      /* strip data (pixels) */
      temp = ftell(fp);
      fseek(fp, loc_0111, SEEK_SET);
      put_dword(fp, temp);
      fseek(fp, 0, SEEK_END);
      switch (img->type)
      {  case IMG_2:
            for (y = 0; y < img->ysize; y++)
            {  temp = 0, k = 0;
               for (x = 0; x < img->xsize; x++)
               {  temp = (temp << 1) | get_imgval(img, x, y), k++;
                  if (k == 8) put_byte(fp, temp), temp = 0, k = 0;
               }
               if (k > 0)
               {  while (k < 8) temp <<= 1, k++;
                  put_byte(fp, temp);
               }
            }
            break;
         case IMG_4:
         case IMG_16:
            for (y = 0; y < img->ysize; y++)
            {  temp = 0, k = 0;
               for (x = 0; x < img->xsize; x++)
               {  temp = (temp << 4) | get_imgval(img, x, y), k += 4;
                  if (k == 8) put_byte(fp, temp), temp = 0, k = 0;
               }
               if (k > 0)
               {  while (k < 8) temp <<= 4, k += 4;
                  put_byte(fp, temp);
               }
            }
            break;
         case IMG_256:
            for (y = 0; y < img->ysize; y++)
            {  for (x = 0; x < img->xsize; x++)
                  put_byte(fp, get_imgval(img, x, y));
            }
            break;
         default:
            insist(img->type != img->type);
      }
      fflush(fp);
      if (ferror(fp))
      {  error("save_as_tiff: can't write to `%s' - %s", fname,
            strerror(errno));
         goto fail;
      }
      fclose(fp);
      return 0;
fail: if (fp != NULL) fclose(fp);
      return 1;
}

/* eof */
