<?php

require_once dirname(__FILE__) . '/../Image.php';

/**
 * This class implements the Horde_Image:: API for PNG images. It
 * mainly provides some utility functions, such as the ability to make
 * pixels or solid images for now.
 *
 * $Horde: horde/lib/Image/png.php,v 1.14 2003/07/25 20:40:37 chuck Exp $
 *
 * Copyright 1999-2003 Mike Cochrane <mike@graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @version $Revision: 1.14 $
 * @since   Horde 3.0
 * @package Horde_Image
 */
class Horde_Image_png extends Horde_Image {

    /* Image Width */
    var $_width = 1;

    /* Image Height */
    var $_height = 1;

    /* Color Depth */
    // only 8 and 16 implemented
    var $_colorDepth = 8;

    /* Color Type */
    // only 2 (true color) implemented
    var $_colorType = 2;

    /* Compression Method */
    // 0 only valid value
    var $_compressionMethod = 0;

    /* Filter Method */
    // 0 only valid value
    var $_filterMethod = 0;

    /* Interlace Method */
    // only 0 (no interlace) implemented
    var $_interlaceMethod = 0;

    function getContentType()
    {
        return 'image/png';
    }

    function getLink($url, $title = '')
    {
        return Horde::img($url, $title, '', '');
    }

    /**
     * Create a single pixel PNG Image.
     */
    function makePixel($rgb = null)
    {
        return $this->makeImage(1, 1, $rgb);
    }

    /**
     * Create a solid color PNG Image of any width.
     */
    function makeImage($width = null, $height = null, $rgb = null)
    {
        if (!is_null($width)) {
            $this->_width = $width;
        }
        if (!is_null($height)) {
            $this->_height = $height;
        }
        if (!is_null($rgb)) {
            $this->_rgb = $rgb;
        }

        $data  = $this->_header();
        $data .= $this->_IHDR();

        /* Optional Line to say what created the image file */
        // $data .= $this->_tEXt('Software','Horde Framework Image_PNG Class');

        /* Optional Line to set the last modified date/time */
        // $data .= $this->_tIME();

        $data .= $this->_IDAT();
        $data .= $this->_IEND();

        return $data;
    }

    function makeBarcode($text)
    {
        $barcodeheight = 40;
        $barcodethinwidth = 2;
        $barcodethickwidth = $barcodethinwidth * 3;
        $codingmap = array('0' => '000110100', '1' => '100100001',
                           '2' => '001100001', '3' => '101100000', '4' => '000110001',
                           '5' => '100110000', '6' => '001110000', '7' => '000100101',
                           '8' => '100100100', '9' => '001100100', 'A' => '100001001',
                           'B' => '001001001', 'C' => '101001000', 'D' => '000011001',
                           'E' => '100011000', 'F' => '001011000', 'G' => '000001101',
                           'H' => '100001100', 'I' => '001001100', 'J' => '000011100',
                           'K' => '100000011', 'L' => '001000011', 'M' => '101000010',
                           'N' => '000010011', 'O' => '100010010', 'P' => '001010010',
                           'Q' => '000000111', 'R' => '100000110', 'S' => '001000110',
                           'T' => '000010110', 'U' => '110000001', 'V' => '011000001',
                           'W' => '111000000', 'X' => '010010001', 'Y' => '110010000',
                           'Z' => '011010000', ' ' => '011000100', '$' => '010101000',
                           '%' => '000101010', '*' => '010010100', '+' => '010001010',
                           '-' => '010000101', '.' => '110000100', '/' => '010100010');
        $text = String::upper($text, true);

        // Add start/stop chars.
        $text = "*$text*";

        $textlen = strlen($text);
        $barcodewidth = ($textlen) * (7 * $barcodethinwidth
                                      + 3 * $barcodethickwidth) - $barcodethinwidth;

        $this->_width = $barcodewidth;
        $this->_height = $barcodeheight;
        $img = array();
        for ($h = 0; $h < $barcodeheight; $h++) {
            for ($w = 0; $w < $barcodewidth; $w++) {
                $img[$h][$w] = array('r' => 255, 'g' => 255, 'b' => 255);
            }
        }

        $xpos = 0;
        for ($idx = 0; $idx < $textlen; $idx++) {
            $char = $text[$idx];
            // Make unknown chars a '-'.
            if (!isset($codingmap[$char])) {
                $char = '-';
            }
            for ($baridx = 0; $baridx <= 8; $baridx++) {
                $elementwidth = $codingmap[$char][$baridx] ?
                    $barcodethickwidth : $barcodethinwidth;
                if (($baridx + 1) % 2) {
                    for ($h = 0; $h < $barcodeheight; $h++) {
                        for ($w = $xpos; $w < $xpos + $elementwidth; $w++) {
                            $img[$h][$w] = array('r' => 0, 'g' => 0, 'b' => 0);
                        }
                    }
                }
                $xpos += $elementwidth;
            }
            $xpos += $barcodethinwidth;
        }

        $im  = $this->_header();
        $im .= $this->_IHDR();

        /* Optional Line to say what created the image file */
        // $data .= $this->_tEXt('Software','Horde Framework Image_PNG Class');

        /* Optional Line to set the last modified date/time */
        // $data .= $this->_tIME();

        $im .= $this->_IDAT($img);
        $im .= $this->_IEND();

        return $im;
    }

    function _header()
    {
        return pack('CCCCCCCC', 137, 80, 78, 71, 13, 10, 26, 10);
    }

    /**
     * Create Image Header block.
     */
    function _IHDR()
    {
        $data = pack('a4NNCCCCC', 'IHDR', $this->_width, $this->_height, $this->_colorDepth, $this->_colorType, $this->_compressionMethod, $this->_filterMethod, $this->_interlaceMethod);
        return pack('Na' . strlen($data) . 'N', strlen($data) - 4, $data, crc32($data));
    }

    /**
     * Create IEND block.
     */
    function _IEND()
    {
        $data = 'IEND';
        return pack('Na' . strlen($data) . 'N', strlen($data) - 4, $data, crc32($data));
    }

    /**
     * Create Image Data block.
     */
    function _IDAT($img = null)
    {
        $rgb = str_replace('#', '', $this->_rgb);

        $r = hexdec(substr($rgb, 0, 2));
        $g = hexdec(substr($rgb, 2, 2));
        $b = hexdec(substr($rgb, 4, 2));

        if (is_null($img)) {
            for ($i = 0; $i < $this->_height; $i++) {
                for ($j = 0; $j < $this->_width; $j++) {
                    $img[$i][$j] = array('r' => $r, 'g' => $g, 'b' => $b);
                }
            }
        }

        $data = '';
        $prevscanline = null;
        $filter = 0;
        for ($i = 0; $i < $this->_height; $i++) {
            $scanline = array();
            $data .= chr($filter);
            for ($j = 0; $j < $this->_width; $j++) {

                if ($this->_colorDepth == 8) {
                    $scanline[$j] = pack('CCC', $img[$i][$j]['r'], $img[$i][$j]['g'], $img[$i][$j]['b']);
                } elseif ($this->_colorDepth == 16) {
                    $scanline[$j] = pack('nnn', $img[$i][$j]['r'] << 8, $img[$i][$j]['g'] << 8 , $img[$i][$j]['b'] << 8 );
                }

                if ($filter == 0) {
                    /* No Filter */
                    $data .= $scanline[$j];
                } elseif ($filter == 2) {
                    /* Up Filter */
                    $pixel = $scanline[$j] - $prevscanline[$j];
                    if ($this->_colorDepth == 8) {
                        $data .= pack('CCC', $pixel >> 16, ($pixel >> 8) & 0xFF, $pixel & 0xFF);
                    } elseif ($this->_colorDepth == 16) {
                        $data .= pack('nnn', ($pixel >> 32), ($pixel >> 16) & 0xFFFF , $pixel & 0xFFFF);
                    }
                }
            }
            $filter = 2;
            $prevscanline = $scanline;
        }
        $compressed = gzdeflate($data, 9);

        $data = 'IDAT' . pack('CCa' . strlen($compressed) . 'a4', 0x78, 0x01, $compressed, $this->_Adler32($data));
        return pack('Na' . strlen($data) . 'N', strlen($data) - 4, $data, crc32($data));
    }

    /**
     * Create tEXt block.
     */
    function _tEXt($keyword, $text)
    {
        $data = 'tEXt' . $keyword . "\0" . $text;
        return pack('Na' . strlen($data) . 'N', strlen($data) - 4, $data, crc32($data));
    }

    /**
     * Create last modified time block.
     */
    function _tIME($date = null)
    {
        if (is_null($date)) {
            $date = time();
        }

        $data = 'tIME' . pack('nCCCCC', intval(date('Y', $date)), intval(date('m', $date)), intval(date('j', $date)), intval(date('G', $date)), intval(date('i', $date)), intval(date('s', $date)));
        return pack('Na' . strlen($data) . 'N', strlen($data) - 4, $data, crc32($data));
    }

    /**
     * Calculate an Adler32 checksum for a string.
     */
    function _Adler32($input)
    {
        $s1 = 1;
        $s2 = 0;
        $iMax = strlen($input);
        for ($i = 0; $i < $iMax; $i++) {
            $s1 = ($s1 + ord($input[$i])) % 0xFFF1;
            $s2 = ($s2 + $s1) % 0xFFF1;
        }
        return pack('N', (($s2 << 16) | $s1));
    }

}
