/* glpimg2.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 "glpimg.h"
#include "glplib.h"

/*----------------------------------------------------------------------
-- set_clipreg - change clipping region.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void set_clipreg(IMG *img, int x1, int y1, int x2, int y2);
--
-- *Description*
--
-- The set_clipreg routine sets the new clipping region of the raster
-- image which is a rectangle area with absolute coordinates (x1,y1) of
-- the left upper corner and (x2,y2) of the right lower corner. */

void set_clipreg(IMG *img, int x1, int y1, int x2, int y2)
{     if (x1 < 0) x1 = 0;
      if (x1 >= img->xsize) x1 = img->xsize - 1;
      img->x1 = x1;
      if (y1 < 0) y1 = 0;
      if (y1 >= img->ysize) y1 = img->ysize - 1;
      img->y1 = y1;
      if (x2 < 0) x2 = 0;
      if (x2 >= img->xsize) x2 = img->xsize - 1;
      img->x2 = x2;
      if (y2 < 0) y2 = 0;
      if (y2 >= img->ysize) y2 = img->ysize - 1;
      img->y2 = y2;
      return;
}

/*----------------------------------------------------------------------
-- set_logorg - change base point.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void set_logorg(IMG *img, int x, int y);
--
-- *Description*
--
-- The set_logorg routine moves the base point of the raster image to
-- the new position with absolute coordinates (x,y).
--
-- The base point may be placed out of image or clipping region. */

void set_logorg(IMG *img, int x, int y)
{     img->x0 = x, img->y0 = y;
      return;
}

/*----------------------------------------------------------------------
-- set_viewport - change view port.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void set_viewport(IMG *img, int x1, int y1, int x2, int y2);
--
-- *Description*
--
-- The set_viewport routine sets the new view port of the raster image
-- which is a rectangle area with absolute coordinates (x1,y1) of the
-- left upper corner and (x2,y2) of the right lower corner. */

void set_viewport(IMG *img, int x1, int y1, int x2, int y2)
{     set_logorg(img, x1, y1);
      set_clipreg(img, x1, y1, x2, y2);
      return;
}

/*----------------------------------------------------------------------
-- set_color - change current color index.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void set_color(IMG *img, int color);
--
-- *Description*
--
-- The set_color routine sets new color index for the raster image as
-- specified by the parameter color. */

void set_color(IMG *img, int color)
{     switch (img->type)
      {  case IMG_2: /* 2 colors (1 bit per pixel) */
            img->color = color & 0x01;
            break;
         case IMG_4: /* 4 colors (2 bits per pixel) */
            img->color = color & 0x03;
            break;
         case IMG_16: /* 16 colors (4 bits per pixel) */
            img->color = color & 0x0F;
            break;
         case IMG_256: /* 256 colors (8 bits per pixel) */
            img->color = color & 0xFF;
            break;
         default:
            insist(img->type != img->type);
      }
      return;
}

/*----------------------------------------------------------------------
-- set_pixel - set pixel to current color index.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void set_pixel(IMG *img, int x, int y);
--
-- *Description*
--
-- The set_pixel routine sets the pixel of the raster image which has
-- relative coordinates (x,y) to the current color index.
--
-- If the pixel (x,y) is out of the clipping region, the routine does
-- nothing. */

void set_pixel(IMG *img, int x, int y)
{     /* convert to absolute coordinates */
      x += img->x0, y += img->y0;
      /* take into account the clipping region */
      if (img->x1 <= x && x <= img->x2 && img->y1 <= y && y <= img->y2)
         set_imgval(img, x, y, img->color);
      return;
}

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

int get_pixel(IMG *img, int x, int y)
{     /* convert to absolute coordinates */
      x += img->x0, y += img->y0;
      /* take into account the clipping region */
      if (img->x1 <= x && x <= img->x2 && img->y1 <= y && y <= img->y2)
         return get_imgval(img, x, y);
      return -1;
}

/*----------------------------------------------------------------------
-- move_to - change current point.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void move_to(IMG *img, int x, int y);
--
-- *Description*
--
-- The move_to routine moves the current point of the raster image to
-- the new position with relative coordinates (x,y).
--
-- The current point may be placed out of image or clipping region. */

void move_to(IMG *img, int x, int y)
{     img->x = img->x0 + x;
      img->y = img->y0 + y;
      return;
}

/*----------------------------------------------------------------------
-- line_to - generate straightforward segment.
--
-- *Synopsis*
--
-- #include "glpimg.h"
-- void line_to(IMG *img, int x, int y);
--
-- *Description*
--
-- The line_to routine generates pixels representing straightforward
-- segment on the raster image. The segment is drawn from the current
-- point up to (and including) the given end point which has relative
-- coordinates (x,y). After operation the end point becomes the new
-- current point of the image. */

static void clip(IMG *img, int x1, int y1, int x2, int y2);

void line_to(IMG *img, int x, int y)
{     int old_x = img->x, old_y = img->y;
      /* change the current point */
      img->x = img->x0 + x; img->y = img->y0 + y;
      /* generate segment taking into account the clipping region */
      clip(img, old_x, old_y, img->x, img->y);
      return;
}

/*----------------------------------------------------------------------
-- dda - straightforward segment generating routine.
--
-- This routine is based on the algorithm borrowed from the book:
-- W.M.Newman, R.F.Sproull. Principles of Interactive Computer Graphics.
-- McGraw-Hill, 1973.
--
-- The name DDA is an abbreviation of Digital Differential Analyzer. */

static void dda(IMG *img, int x1, int y1, int x2, int y2)
{     int deltax, deltay, steps, xchange, ychange;
      double rem, slope;
      set_imgval(img, x1, y1, img->color);
      if (x1 == x2 && y1 == y2) goto done;
      deltax = x2 - x1; deltay = y2 - y1;
      rem = 0.5; /* preload with 1/2 */
      xchange = deltax > 0 ? +1 : -1;
      ychange = deltay > 0 ? +1 : -1;
      if (deltax < 0) deltax = - deltax;
      if (deltay < 0) deltay = - deltay;
      if (deltax > deltay) /* x is greater */
      {  slope = (double)deltay / (double)deltax;
         for (steps = 1; steps <= deltax; steps++)
         {  rem += slope;
            if (rem >= 1.0)
            {  y1 += ychange;
               rem -= 1.0;
            }
            x1 += xchange;
            /* put point on display */
            set_imgval(img, x1, y1, img->color);
         }
      }
      else /* y is greater */
      {  slope = (double)deltax / (double)deltay;
         for (steps = 1; steps <= deltay; steps++)
         {  rem += slope;
            if (rem >= 1.0)
            {  x1 += xchange;
               rem -= 1.0;
            }
            y1 += ychange;
            /* put point on display */
            set_imgval(img, x1, y1, img->color);
         }
      }
done: return;
}

/*----------------------------------------------------------------------
-- clip - clipping routine.
--
-- This routine is based on the algorithm borrowed from the book:
-- W.M.Newman, R.F.Sproull. Principles of Interactive Computer Graphics.
-- McGraw-Hill, 1973.
--
-- The algorithm consists of two parts. The first part determines
-- whether the segment is fully on the screen, and if not, whether it
-- is possible to drop it as placed fully out of the screen. If neither
-- condition is true, the segment is divided on two parst, and the test
-- is applied to each of them. The algorithm is based on idea that any
-- segment either is fully on the screen or it may be divided in order
-- to reject one of its parts in easiest way.
--
-- To perform the dropping test edges of the screen are continued in
-- order that they divide entire surface to nine areas:
--
--         |        |
--   0101  |  0100  |  0110
--         |        |
-- --------+--------+--------
--         | Screen |
--   0001  |  0000  |  0010
--         |        |
-- --------+--------+--------
--         |        |
--   1001  |  1000  |  1010
--         |        |
--
-- Each of these areas has 4-bit code and two end points of the segment
-- have codes of those areas where they are placed. Code bits have the
-- following meaning:
--
-- ...1  point is on the left of the left edge of the screen
-- ..1.  point is on the right of the right edge of the screen
-- .1..  point is above of the top edge of the screen
-- 1...  point is below of the bottom edge of the screen
--
-- It is obvious that the segment is fully on the screen if its end
-- points have zero codes. The following condition is less obvious: if
-- logical "and" of two codes is non-zero, the segment is fully out of
-- the screen.
--
-- If the dropping test can't say what to do, the segment should be
-- divided. The easiest way is to determine the point where the segment
-- intersects the screen edge and to drop that part which is fully out
-- of the screen. For eaxmple, the segment AB
--
--   A
-- *
--   \  C
-- - - * - - +-----------------+
--       \   |                 |
--         \ | D               |
--           *                 |
--           | \               |
--           |   \  B          |
--           |     *           |
--           |                 |
--           +-----------------+
--
-- can be divided by the point C that allows to drop the part AC. It
-- is still unclear what to do with new segment BC, so further testing
-- is continued and the segment BC is divided by the point D. The next
-- test shows that the segment BD is fully on the screen. In some cases
-- it is not clear by what point the segment should be divided - by the
-- point C or by D. However, if dropping test is repeated, the final
-- results will be the same. */

static int code(IMG *img, int x, int y)
{     /* determine 4-bit code of the given end point */
      return (x < img->x1 ? 0x0001 : x > img->x2 ? 0x0010 : 0x0000) |
             (y < img->y1 ? 0x0100 : y > img->y2 ? 0x1000 : 0x0000);
}

static void clip(IMG *img, int x1, int y1, int x2, int y2)
{     /* generate the segment with (absolute) coordinates (x1,y1) and
         (x2,y2) taking into account the clipping region */
      int c1, c2, temp;
      double ratio;
      c1 = code(img, x1, y1);
      c2 = code(img, x2, y2);
      while (c1 | c2)
      {  /* compute */
         if (c1 & c2) return; /* no part of the line visible */
         if (c1 == 0)
         {  /* excahnge points 1 and 2 */
            temp = c1; c1 = c2; c2 = temp;
            temp = x1; x1 = x2; x2 = temp;
            temp = y1; y1 = y2; y2 = temp;
         }
         if (c1 & 0x0001)
         {  /* push toward left edge */
            ratio = (double)(img->x1 - x1) / (double)(x2 - x1);
            y1 += (int)(ratio * ((double)y2 - (double)y1) + 0.5);
            x1 = img->x1;
         }
         else if (c1 & 0x0010)
         {  /* push toward right edge */
            ratio = (double)(img->x2 - x1) / (double)(x2 - x1);
            y1 += (int)(ratio * ((double)y2 - (double)y1) + 0.5);
            x1 = img->x2;
         }
         else if (c1 & 0x0100)
         {  /* push toward top edge */
            ratio = (double)(img->y1 - y1) / (double)(y2 - y1);
            x1 += (int)(ratio * ((double)x2 - (double)x1) + 0.5);
            y1 = img->y1;
         }
         else if (c1 & 0x1000)
         {  /* push toward bottom edge */
            ratio = (double)(img->y2 - y1) / (double)(y2 - y1);
            x1 += (int)(ratio * ((double)x2 - (double)x1) + 0.5);
            y1 = img->y2;
         }
         c1 = code(img, x1, y1); /* recompute the code */
      }
      /* if we reach here, the line from (x1,y1) to (x2,y2) is visible
         and may be drawn */
      dda(img, x1, y1, x2, y2);
      return;
}

/* eof */
