<?php

/* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Evgeny Stepanischev <imbolk@gmail.com>                      |
// +----------------------------------------------------------------------+
// Project home page (Russian): http://bolknote.ru/files/xbm/
//
// $Id$

require_once "PEAR.php";

/**
 * Black color constant
 */
define ('IMAGE_XBM_BLACK', 0);

/**
 * White color constant
 */
define ('IMAGE_XBM_WHITE', 1);

/**
 * Transparency
 */
define ('IMAGE_XBM_TRANS', 2);

class Image_XBM {

   /**
    * Picture width
    *
    * @var integer
    *
    * @access private
    */
    var $_sx;

   /**
    * Picture height
    *
    * @var integer
    *
    * @access private
    */
    var $_sy;

   /**
    * Picture itself
    *
    * @var array
    *
    * @access private
    */
    var $_image;

   /**
    * Contains style for line drawing
    *
    * @var array
    *
    * @access private
    */
    var $_style;

   /**
    * Position counter in $_style array
    *
    * @var integer
    *
    * @access private
    */
    var $_stylecnt;

    /**
     * Constructor. Init all variables.
     *
     * @access public
     */
    function Image_XBM()
    {
        $this->_sx = 0;
        $this->_sy = 0;
        $this->_image = NULL;

        $this->_style = array(1);
        $this->_stylecnt = 0;

        if (!defined('IMG_COLOR_STYLED')) {
                define ('IMG_COLOR_STYLED', -1);
                }
    }

    /**
     * Create a new image
     *
     * @param int $x_size image width
     * @param int $y_size image height
     * @access public
     * @return bool always true
     */
    function create($x_size, $y_size)
    {
        $this->_sx = $x_size;
        $this->_sy = $y_size;


        $col = array_pad (array(), $y_size, 0);
        $this->_image = array_pad (array(), ceil($x_size / 8), $col);

        return true;
    }

    /**
     * Get image width
     *
     * @return int returns the width of the image
     * @access public
     */
    function getWidth()
    {
        return $this->_sx;
    }

    /**
     * Get image height
     *
     * @return int returns the height of the image
     * @access public
     */
    function getHeight()
    {
        return $this->_sy;
    }

    /**
     * Draw a line
     *
     * @param int $x1 from X
     * @param int $y1 from Y
     * @param int $x2 to X
     * @param int $y2 to Y
     * @param int $color line color
     * @return bool always true
     * @access public
     */
    function drawLine($x1, $y1, $x2, $y2, $color)
    {
        $step = 0;
        $dx   = abs($x2 - $x1);
        $sx   = ($x2 - $x1) > 0 ? 1 : -1;
        $dy   = abs($y2 - $y1);
        $sy   = ($y2 - $y1) > 0 ? 1 : -1;

        if ($dy > $dx) {
            $step = 1;
            list ($x1, $y1) = array ($y1, $x1);
            list ($dx, $dy) = array ($dy, $dx);
            list ($sx, $sy) = array ($sy, $sx);
        }


        $e = 2 * $dy - $dx;

        for ($i = 0; $i < $dx; ++$i) {
            $step ? $this->drawPixel ($y1, $x1, $color) :
                    $this->drawPixel ($x1, $y1, $color);

            while ($e >= 0) {
                $y1 += $sy;
                $e  -= 2 * $dx;
            }

            $x1 += $sx;
            $e  += 2 * $dy;
        }

        $this->drawPixel ($x2, $y2, $color);
        return true;
    }

    /**
     * Draw a single pixel
     *
     * @param int $x X ordinate
     * @param int $y Y ordinate
     * @param int $color pixel color
     * @return bool always true
     * @access public
     */
    function drawPixel($x, $y, $color)
    {
        if ($x >= 0 && $y >= 0 && $x < $this->_sx && $y < $this->_sy && $color <> IMAGE_XBM_TRANS) {

            if ($color == IMG_COLOR_STYLED) {
                $color = $this->_style[$this->_stylecnt++];

                if ($this->_stylecnt >= sizeof($this->_style)) {
                        $this->_stylecnt = 0;
                                }
            }

            if ($color) {
                    $this->_image[$x >> 3][$y] &= 255 ^ (1 << ($x % 8));
                        } else {
                    $this->_image[$x >> 3][$y] |= 1 << ($x % 8);
                        }
        }

        return true;
    }

    /**
     * Get the index of the color of a pixel
     *
     * @param int $x X ordinate
     * @param int $y Y ordinate
     * @return mixed returns color of the pixel or null if pixel out of bounds
     * @access public
     */
    function getColorAt($x, $y)
    {
        if ($x >= 0 && $y >= 0 && $x < $this->_sx && $y < $this->_sy) {
                return $this->_image[$x >> 3][$y] & (1 << ($x % 8)) ? 0 : 1;
                }

        return null;
    }

    /**
     * Output image to browser or file
     *
     * @param string $filename (optional) filename for output
     * @return bool PEAR_Error or true
     * @access public
     */
    function output($filename = false)
    {
        $wx = ceil($this->_sx / 8);

        $s = "#define _width ".$this->_sx."\n".
             "#define _height ".$this->_sy."\n".
             "static unsigned char _bits[] = {\n";

        for ($y = 0; $y < $this->_sy; ++$y) {
                for ($x = 0; $x < $wx; ++$x) {
                        $s .= '0x'.sprintf('%02x', $this->_image[$x][$y]).', ';
                        }
                }

        $s = rtrim ($s, ', ')."\n}";

        if ($filename === false) {
            echo $s;
        } else {
            if ($fp = fopen($filename, 'w')) {
                flock($fp, LOCK_EX);

                fwrite($fp, $s);
                fclose($fp);
            } else {
                    return PEAR::raiseError('Cannot open file for writting.', 5);
            }
        }

        return true;
    }

    /**
     * Draw a rectangle
     *
     * @param int $x1 left coordinate
     * @param int $y1 top coordinate
     * @param int $x2 right coordinate
     * @param int $y2 bottom coordinate
     * @param int $color drawing color
     * @return bool always true
     * @access public
     */
    function drawRectangle($x1, $y1, $x2, $y2, $color)
    {
        $this->drawline ($x1, $y1, $x2, $y1, $color);
        $this->drawline ($x2, $y1, $x2, $y2, $color);
        $this->drawline ($x2, $y2, $x1, $y2, $color);
        $this->drawline ($x1, $y2, $x1, $y1, $color);

        return true;
    }

    /**
     * Draw a filled rectangle
     *
     * @param int $x1 left coordinate
     * @param int $y1 top coordinate
     * @param int $x2 right coordinate
     * @param int $y2 bottom coordinate
     * @param int $color drawing color
     * @return bool always true
     * @access public
     */
    function drawFilledRectangle($x1, $y1, $x2, $y2, $color)
    {
        $hx = min(max($x1, $x2), $this->_sx - 1);
        $lx = max(min($x1, $x2), 0);

        for ($x = $lx; $x <= $hx; ++$x) {
            $this->drawline ($x, $y1, $x, $y2, $color);
        }

        return true;
    }

    /**
     * Draw a polygon
     *
     * @param array $points is a PHP array containing the polygon's vertices
     * @param int $num_points is the total number of points (vertices).
     * @param int $color drawing color
     * @return bool always true
     * @access public
     */
    function drawPolygon($points, $num_points, $color)
    {
        if ($num_points > 2) {
            for ($i = 0; $i<$num_points - 1; ++$i) {
                $j = $i * 2;

                $this->drawline($points[$j], $points[$j+1], $points[$j+2], $points[$j+3], $color);
            }

            $this->drawline($points[0], $points[1], $points[$num_points * 2 - 2], $points[$num_points * 2 - 1], $color);
        }

        return true;
    }

    /**
     * Destroy an image
     *
     * @return bool always true
     * @access public
     */
    function destroy()
    {
        $this->Image_XBM();
        return true;
    }

    /**
     * Sets the style to be used by all line drawing functions
     * (such as imageline() and imagepolygon()) when drawing
     * with the special color IMG_COLOR_STYLED
     *
     * @param array $style is an array of pixels.
     * @return bool always true
     * @access public
     */
    function setStyle($style)
    {
        if (!is_array($style)) {
                $style = array($style);
                }

        $this->_style = $style;
        return true;
    }

    /**
     * Flood fill
     *
     * @param int $x starting coordinate
     * @param int $y starting coordinate
     * @param int $color color
     * @return bool always true
     * @access public
     */
    function fill($x, $y, $color)
    {
        $color = $color ? 1 : 0;

        $stack = array(array($x, $y));

        while (sizeof($stack)) {
            list ($x, $y) = array_pop($stack);

            if ($this->getColorAt($x, $y) <> $color) {
                        $this->drawPixel($x, $y, $color);
                        }

            if (($px = $this->getColorAt($sx = $x + 1, $y)) <> $color && $px !== null) {
                $stack[] = array($sx, $y);
            }

            if (($px = $this->getColorAt($x, $sy = $y + 1)) <> $color && $px !== null) {
                $stack[] = array($x, $sy);
            }

            if (($px = $this->getColorAt($sx = $x - 1, $y)) <> $color && $px !== null) {
                $stack[] = array($sx, $y);
            }

            if (($px = $this->getColorAt($x, $sy = $y - 1)) <> $color && $px !== null) {
                $stack[] = array($x, $sy);
            }
        }

        return true;
    }

    /**
     * Copy part of an image
     *
     * @param object $src_im destination Image_XBM object
     * @param int $dst_x starting x coordinate in the destination
     * @param int $dst_y starting y coordinate in the destination
     * @param int $src_x starting x coordinate in the source
     * @param int $src_y starting y coordinate in the source
     * @param int $src_w width of portion
     * @param int $src_h height of portion
     * @return bool always true
     * @access public
     */
    function copy(&$src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h)
    {
        // If pictures are equals then just copy
        if (!$dst_x && !$dst_y && $src_w == $this->_sx - 1 &&
        $src_h == $this->_sy - 1 && $src_im->_sx == $this->_sx &&
        $src_im->_sy == $this->_sy) {
            $this->_image = $src_im->_image;
            return true;
        }

        // Fast copy
        if ($src_x % 8 == $dst_x % 8) {
            $stop_x  = ($src_w >> 3) * 8;
            $start_x = $src_x % 8;

            $src_h = min($src_h, $this->_sy - $dst_y - 1, $src_im->_sy - $src_y - 1);
            $src_w = min($src_w, $this->_sx - $dst_x - 1, $src_im->_sx - $src_x - 1);

            for ($j = 0; $j < $src_h; ++$j) {
                for ($i = 0; $i < $src_w; ++$i) {
                    if ($i < $start_x || $i >= $stop_x) {
                        $this->drawPixel($dst_x + $i, $dst_y + $j, $src_im->getColorAt($src_x + $i, $src_y + $j));
                    } else {
                        $this->_image[($i+$dst_x) >> 3][$dst_y + $j] = $src_im->_image[($i + $src_x) >> 3][$src_y + $j];
                        $i += 7;
                    }
                }
            }

            return true;
        }

        // Slow copy
        for ($i = 0; $i < $src_w; ++$i) {
            for ($j = 0; $j < $src_h; ++$j) {
                $this->drawPixel($dst_x + $i, $dst_y + $j, $src_im->getColorAt($src_x + $i, $src_y + $j));
            }
        }

        return true;
    }

    /**
     * Create a new image from XBM file or URL
     *
     * @param string $filename XBM file name or URL
     * @return mixed PEAR_error or true for success
     * @access public
     */
    function createFromFile($filename)
    {
        $fp = fopen($filename, 'r');
        if (!is_resource($fp)) {
            return PEAR::raiseError('Cannot open file.', 4);
        }

        for ($i = 0; $i < 2; ++$i) {
            if (feof($fp)) {
                fclose($fp);
                return PEAR::raiseError('Invalid XBM file.', 5);
            }

            $line = fgets($fp, 1024);
            if (preg_match('/^#define\s+.*?_(width|height)\s+(\d+)/', $line, $match)) {
                ${$match[1]} = $match[2];
            }
        }

        if (feof($fp) || !isset($width) || !isset($height)) {
            fclose($fp);
            return PEAR::raiseError('Invalid XBM file.', 5);
        }

        $picture = preg_replace('/^\s*static\s+(?:unsigned\s+)char\s+.*?_bits\[\]\s*=\s*\{/', '', fread($fp, @filesize($filename)));
        $picture = preg_replace('/\s*\};?\s*$/', '', $picture);

        $this->create($width, $height);
        $picture = explode(',', $picture);
        $sx = ceil($width / 8);

        for ($s = $j = 0; $j < $height; ++$j) {
            for ($i = 0; $i < $sx; ++$i) {
                $this->_image[$i][$j] = hexdec($picture[$s++]);
            }
        }

        fclose($fp);

        return true;
    }

    /**
     * Draw an ellipse
     *
     * @param int $cx center of ellipse (X coordinate)
     * @param int $cy center of ellipse (Y coordinate)
     * @param int $w width of ellipse
     * @param int $h height of ellipse
     * @param int $color color for drawing
     * @return bool always true
     * @access public
     */
    function drawEllipse($cx, $cy, $w, $h, $color)
    {
        return $this->_ellipse($cx, $cy, $w, $h, $color);
    }

    /**
     * Draw a filled ellipse
     *
     * @param int $cx center of ellipse (X coordinate)
     * @param int $cy center of ellipse (Y coordinate)
     * @param int $w width of ellipse
     * @param int $h height of ellipse
     * @param int $color color for drawing
     * @return bool always true
     * @access public
     */
    function drawFilledEllipse($cx, $cy, $w, $h, $color)
    {
        return $this->_ellipse($cx, $cy, $w, $h, $color, true);
    }

    /**
     * To draw a text string over an image using Figlet fonts
     *
     * @param string $text text for drawing
     * @param string $font font file name
     * @param mixed $size integer (font ratio) or string 'X:Y' (X size ratio and Y size ratio)
     * @param int $fgcolor foreground font color
     * @param int $bgcolor background font color
     * @param int $x start drawing from
     * @param int $y start drawing from
     * @return mixed PEAR_error or true for success
     * @access public
     */
    function drawFigletText($text, $font, $size, $fgcolor, $bgcolor, $x, $y)
    {
        if (is_numeric($size)) {
            $x_ratio = $y_ratio = $size;
        } else {
            @list($x_ratio, $y_ratio) = preg_split('/[:x]/', $size, 2);
        }

        if (!$x_ratio || !$y_ratio) {
            return PEAR::raiseError('Invalid font ratio.', 3);
        }

        require_once 'Text/Figlet.php';
        $figlet = &new Text_Figlet();

        if (PEAR::isError($error = $figlet->LoadFont($font))) {
            return PEAR::raiseError('Font loading error.', 2);
        }

        $lines = explode("\n", $figlet->lineEcho($text));
        $cnt   = sizeof($lines);

        for ($i = 0; $i < $cnt; ++$i) {
            for ($j = 0; $j < strlen($lines[$i]); ++$j) {
                $c = $lines[$i]{$j} <> ' ' ? $fgcolor : $bgcolor;

                for ($rx = 0; $rx < $x_ratio; ++$rx) {
                    for ($ry = 0; $ry < $y_ratio; ++$ry) {
                        $this->drawPixel($x + $j * $x_ratio + $rx, $y + $i * $y_ratio + $ry, $c);
                    }
                }
            }
        }

        return true;
    }

    /**
     * Draw a ellipse
     *
     * @param int $x center of ellipse (X coordinate)
     * @param int $y center of ellipse (Y coordinate)
     * @param int $a width of ellipse
     * @param int $b height of ellipse
     * @param int $color color for drawing
     * @param bool $filled (optional) filled ellipse or not
     * @return bool always true
     * @access private
     */
    function _ellipse($x, $y, $a, $b, $color, $filled = false)
    {
        $a_square = $a * $a;
        $b_square = $b * $b;

        $row = $b;
        $col = 0;
        $two_a_square  = $a_square << 1;
        $four_a_square = $a_square << 2;
        $two_b_square  = $b_square << 1;
        $four_b_square = $b_square << 2;

        $d = $two_a_square*(($row - 1) * $row) + $a_square + $two_b_square * (1 - $a_square);
        while ($a_square * $row > $b_square * $col) {
            if ($filled) {
                $this->drawline($col + $x, $row + $y, $col + $x, $y - $row, $color);
                $this->drawline($x - $col, $row + $y, $x - $col, $y - $row, $color);
            } else {
                $this->drawPixel($col + $x, $row + $y, $color);
                $this->drawPixel($col + $x, $y - $row, $color);
                $this->drawPixel($x - $col, $row + $y, $color);
                $this->drawPixel($x - $col, $y - $row, $color);
            }

            if ($d >= 0) {
                $row--;
                $d -= $four_a_square*$row;
            }

            $d += $two_b_square*(3 + ($col << 1));
            ++$col;
        }

        $d = $two_b_square * ($col + 1) * $col + $two_a_square *
        ($row * ($row - 2) + 1) + (1 - $two_a_square) * $b_square;

        while ($row + 1) {
            if ($filled) {
                $this->drawline($col + $x, $row + $y, $col + $x, $y - $row, $color);
                $this->drawline($x - $col, $row + $y, $x - $col, $y - $row, $color);
            } else {
                $this->drawPixel($col + $x, $row + $y, $color);
                $this->drawPixel($col + $x, $y - $row, $color);
                $this->drawPixel($x - $col, $row + $y, $color);
                $this->drawPixel($x - $col, $y - $row, $color);
            }

            if ($d <= 0) {
                ++$col;
                $d += $four_b_square * $col;
            }

            $row--;
            $d += $two_a_square * (3 - ($row << 1));
        }

        return true;
    }
}
?>