<?php

/**
 * Класс для работы с файлами формата ICO
 */
class ico {

    var $bgcolor = array(255, 255, 255);
    var $bgcolor_transparent = false;

    function __construct($path = '') {
        if (strlen($path) > 0) {
            $this->LoadFile($path);
        }
    }

    function LoadFile($path) {
        $this->_filename = $path;
        if (($fp = @fopen($path, 'rb')) !== false) {
            $data = '';
            while (!feof($fp)) {
                $data .= fread($fp, 4096);
            }
            fclose($fp);

            return $this->LoadData($data);
        }
        return false;
    }

    function LoadData($data) {
        $this->formats = array();

        $icodata = unpack("SReserved/SType/SCount", $data);
        $icodata['Count'] = min($icodata['Count'], 100);
        
        $this->ico = $icodata;
        $data = substr($data, 6);

        for ($i = 0; $i < $this->ico['Count']; $i++) {
            $icodata = unpack("CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset", $data);
            if ($icodata['Height'] == 0)
                $icodata['Height'] = 256;
            if ($icodata['Width'] == 0)
                $icodata['Width'] = 256;
            $icodata['FileOffset'] -= ($this->ico['Count'] * 16) + 6;
            if ($icodata['ColorCount'] == 0)
                $icodata['ColorCount'] = 256;
            $this->formats[] = $icodata;
            $data = substr($data, 16);
        }

        for ($i = 0; $i < count($this->formats); $i++) {
            if (substr($data, $this->formats[$i]['FileOffset'], 8) == "\x89PNG\x0D\x0A\x1A\x0A") {
                $this->formats[$i]['isPNG'] = true;
                $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'], $this->formats[$i]['SizeInBytes']);
                $this->formats[$i]['data_length'] = $this->formats[$i]['SizeInBytes'];
                continue;
            }
            $this->formats[$i]['isPNG'] = false;
            $icodata = unpack("LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant", substr($data, $this->formats[$i]['FileOffset']));
            $this->formats[$i]['header'] = $icodata;
            $this->formats[$i]['colors'] = array();
            $this->formats[$i]['BitCount'] = $this->formats[$i]['header']['BitCount'];

            switch ($this->formats[$i]['BitCount']) {
                case 32:
                case 24:
                    $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] * ($this->formats[$i]['BitCount'] / 8);
                    $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + $this->formats[$i]['header']['Size'], $length);
                    break;
                case 8:
                case 4:
                    $icodata = substr($data, $this->formats[$i]['FileOffset'] + $icodata['Size'], $this->formats[$i]['ColorCount'] * 4);
                    $offset = 0;
                    for ($j = 0; $j < $this->formats[$i]['ColorCount']; $j++) {
                        $this->formats[$i]['colors'][] = array(
                            'red'                          => ord($icodata[$offset]),
                            'green'                        => ord($icodata[$offset + 1]),
                            'blue'                         => ord($icodata[$offset + 2]),
                            'reserved'                     => ord($icodata[$offset + 3])
                        );
                        $offset += 4;
                    }
                    $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] * (1 + $this->formats[$i]['BitCount']) / $this->formats[$i]['BitCount'];
                    $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + ($this->formats[$i]['ColorCount'] * 4) + $this->formats[$i]['header']['Size'], $length);
                    break;
                case 1:
                    $icodata = substr($data, $this->formats[$i]['FileOffset'] + $icodata['Size'], $this->formats[$i]['ColorCount'] * 4);
                    $this->formats[$i]['colors'][] = array(
                        'blue'                         => ord($icodata[0]),
                        'green'                        => ord($icodata[1]),
                        'red'                          => ord($icodata[2]),
                        'reserved'                     => ord($icodata[3])
                    );
                    $this->formats[$i]['colors'][] = array(
                        'blue'                            => ord($icodata[4]),
                        'green'                           => ord($icodata[5]),
                        'red'                             => ord($icodata[6]),
                        'reserved'                        => ord($icodata[7])
                    );
                    $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] / 8;
                    $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + $this->formats[$i]['header']['Size'] + 8, $length);
                    break;
            }
            $this->formats[$i]['data_length'] = strlen($this->formats[$i]['data']);
        }

        return true;
    }

    function TotalIcons() {
        return count($this->formats);
    }

    function GetIconInfo($index) {
        if (isset($this->formats[$index])) {
            return $this->formats[$index];
        }
        return false;
    }

    function SetBackground($red = 255, $green = 255, $blue = 255) {
        if (is_string($red) && preg_match('/^\#[0-9a-f]{6}$/', $red)) {
            $green = hexdec($red[3] . $red[4]);
            $blue = hexdec($red[5] . $red[6]);
            $red = hexdec($red[1] . $red[2]);
        }
        $this->bgcolor = array($red, $green, $blue);
    }

    function SetBackgroundTransparent($is_transparent = true) {
        return ($this->bgcolor_transparent = $is_transparent);
    }

    function &GetIcon($index) {
        if (!isset($this->formats[$index])) {
            return false;
        }
        if ($this->formats[$index]['isPNG']) {
            if ($this->bgcolor_transparent)
                return imagecreatefromstring($this->formats[$index]['data']);
            $im = imagecreatetruecolor($this->formats[$index]['Width'], $this->formats[$index]['Height']);
            $bgcolor = $this->AllocateColor($im, $this->bgcolor[0], $this->bgcolor[1], $this->bgcolor[2]);
            imagefilledrectangle($im, 0, 0, $this->formats[$index]['Width'], $this->formats[$index]['Height'], $bgcolor);
            $png = imagecreatefromstring($this->formats[$index]['data']);
            imagecopy($im, $png, 0, 0, 0, 0, $this->formats[$index]['Width'], $this->formats[$index]['Height']);
            imagedestroy($png);
            return $im;
        }

        $im = imagecreatetruecolor($this->formats[$index]['Width'], $this->formats[$index]['Height']);

        $bgcolor = $this->AllocateColor($im, $this->bgcolor[0], $this->bgcolor[1], $this->bgcolor[2]);
        imagefilledrectangle($im, 0, 0, $this->formats[$index]['Width'], $this->formats[$index]['Height'], $bgcolor);

        if ($this->bgcolor_transparent) {
            imagecolortransparent($im, $bgcolor);
        }

        if (in_array($this->formats[$index]['BitCount'], array(1, 4, 8, 24))) {
            if ($this->formats[$index]['BitCount'] != 24) {
                /**
                 * color pallete
                 */
                $c = array();
                for ($i = 0; $i < $this->formats[$index]['ColorCount']; $i++) {
                    $c[$i] = $this->AllocateColor($im, $this->formats[$index]['colors'][$i]['red'], $this->formats[$index]['colors'][$i]['green'], $this->formats[$index]['colors'][$i]['blue'], round($this->formats[$index]['colors'][$i]['reserved'] / 255 * 127));
                }
            }

            $width = $this->formats[$index]['Width'];
            if (($width % 32) > 0) {
                $width += (32 - ($this->formats[$index]['Width'] % 32));
            }
            $offset = $this->formats[$index]['Width'] * $this->formats[$index]['Height'] * $this->formats[$index]['BitCount'] / 8;
            $total_bytes = ($width * $this->formats[$index]['Height']) / 8;
            $bits = '';
            $bytes = 0;
            $bytes_per_line = ($this->formats[$index]['Width'] / 8);
            $bytes_to_remove = (($width - $this->formats[$index]['Width']) / 8);
            for ($i = 0; $i < $total_bytes; $i++) {
                $bits .= str_pad(decbin(ord($this->formats[$index]['data'][$offset + $i])), 8, '0', STR_PAD_LEFT);
                $bytes++;
                if ($bytes == $bytes_per_line) {
                    $i += $bytes_to_remove;
                    $bytes = 0;
                }
            }
        }

        switch ($this->formats[$index]['BitCount']) {
            case 32:

                $offset = 0;
                for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) {
                    for ($j = 0; $j < $this->formats[$index]['Width']; $j++) {
                        $color = substr($this->formats[$index]['data'], $offset, 4);
                        if (ord($color[3]) > 0) {
                            $c = $this->AllocateColor($im, ord($color[2]), ord($color[1]), ord($color[0]), 127 - round(ord($color[3]) / 255 * 127));
                            imagesetpixel($im, $j, $i, $c);
                        }
                        $offset += 4;
                    }
                }
                break;
            case 24:

                $offset = 0;
                $bitoffset = 0;
                for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) {
                    for ($j = 0; $j < $this->formats[$index]['Width']; $j++) {
                        if ($bits[$bitoffset] == 0) {
                            $color = substr($this->formats[$index]['data'], $offset, 3);
                            $c = $this->AllocateColor($im, ord($color[2]), ord($color[1]), ord($color[0]));
                            imagesetpixel($im, $j, $i, $c);
                        }
                        $offset += 3;
                        $bitoffset++;
                    }
                }
                break;
            case 8:

                $offset = 0;
                for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) {
                    for ($j = 0; $j < $this->formats[$index]['Width']; $j++) {
                        if ($bits[$offset] == 0) {
                            $color = ord(substr($this->formats[$index]['data'], $offset, 1));
                            imagesetpixel($im, $j, $i, $c[$color]);
                        }
                        $offset++;
                    }
                }
                break;
            case 4:

                $offset = 0;
                $maskoffset = 0;
                $leftbits = true;
                for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) {
                    for ($j = 0; $j < $this->formats[$index]['Width']; $j++) {
                        if ($leftbits) {
                            $color = substr($this->formats[$index]['data'], $offset, 1);
                            $color = array(
                                'High' => bindec(substr(decbin(ord($color)), 0, 4)),
                                'Low'  => bindec(substr(decbin(ord($color)), 4))
                            );
                            if ($bits[$maskoffset++] == 0) {
                                imagesetpixel($im, $j, $i, $c[$color['High']]);
                            }
                            $leftbits = false;
                        } else {
                            if ($bits[$maskoffset++] == 0) {
                                imagesetpixel($im, $j, $i, $c[$color['Low']]);
                            }
                            $offset++;
                            $leftbits = true;
                        }
                    }
                }
                break;
            case 1:

                $colorbits = '';
                $total = strlen($this->formats[$index]['data']);
                for ($i = 0; $i < $total; $i++) {
                    $colorbits .= str_pad(decbin(ord($this->formats[$index]['data'][$i])), 8, '0', STR_PAD_LEFT);
                }
                $total = strlen($colorbits);
                $offset = 0;
                for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) {
                    for ($j = 0; $j < $this->formats[$index]['Width']; $j++) {
                        if ($bits[$offset] == 0) {
                            imagesetpixel($im, $j, $i, $c[$colorbits[$offset]]);
                        }
                        $offset++;
                    }
                }
                break;
        }

        return $im;
    }

    function AllocateColor(&$im, $red, $green, $blue, $alpha = 0) {
        $c = imagecolorexactalpha($im, $red, $green, $blue, $alpha);
        if ($c >= 0) {
            return $c;
        }
        return imagecolorallocatealpha($im, $red, $green, $blue, $alpha);
    }

}