Articles  >  Programming  >  Working with favicons in PHP

The individual favicons mode for each site came into style not so long ago (2004-2005) and in the first place it was connected to the fact that the biggest Russian search system "Yandex" started indexing site icons and show them on the left hand side of every search point result (though technically the display support of icons appeared earlier in the most popular browsers).

Indeed, bright and beautiful image in "Yandex" search results sometimes attracts user's attention and appears to be an additional means of attracting visitors to the site.

You can access the basic information on favicons in section "Questions and answers" of a popular web-resource Favicon.Ru. This article is devoted to downloading techniques and saving favicons in PNG format. Our programmers for the first time encountered this problem when developing CS Yazzle program website (on the right hand side of this page you can see the information block "Partners" where you can view icons and links to every partner-website). It's clear that working with the large amounts of sites is rather difficult and takes a lot of time to manually copy icons and convert them into another format.

For work with site icons our "ControlStyle" studio specialists developed the whole PHP class which allows:

  • to define the icon source by entered site address (URL) if it has a non-standard name and lies not in the root directory of the site;
  • to check if the site icon has been updated since the time when it was made (with the help of HTTP-header "If-Modified-Since");
  • to get site icons created in ICO format (with the depth from 1 to 24 bites)
  • and also in GIF and PNG formats;
  • to save icons in PNG format.

The source code of PHP-class "favicon":

<?php

################################################################################
#   
#    Favicon Class (work with favicons), ver. 1.0, June, 2006.
#   
#    (c) 2006 ControlStyle Company. All rights reserved.
#    Developped by Nikolay I. Yarovoy, Dmitry V. Domojilov.
#
#    http://www.controlstyle.com
#    info@controlstyle.com
#
################################################################################

class favicon
{
    var $ver = '1.1';   
    var $site_url = ''; # url of site
    var $if_modified_since = 0; # cache
    var $is_not_modified = false;
    var $ico_type = 'ico'; # ico, gif or png only
    var $ico_url = ''; # full uri to favicon
    var $ico_exists = 'not checked'; # no comments
    var $ico_data = ''; # ico binary data
    var $output_data = ''; # output image binary data

    # main proc
    function favicon($site_url, $if_modified_since = 0)
    {       
        $site_url = trim(str_replace('http://', '', trim($site_url)), '/');
        $site_url = explode('/', $site_url);
        $site_url = 'http://' . $site_url[0] . '/';
        $this->site_url = $site_url;
        $this->if_modified_since = $if_modified_since;
        $this->get_ico_url();
        $this->is_ico_exists();
        $this->get_ico_data();
    }

    # get uri of favicon
    function get_ico_url()
    {
        if ($this->ico_url == '')
        {
            $this->ico_url = $this->site_url . 'favicon.ico';
       
            # get html of page
            $h = @fopen($this->site_url, 'r');
            if ($h)
            {
                $html = '';
                while (!feof($h) and !preg_match('/<([s]*)body([^>]*)>/i', $html))
                {
                    $html .= fread($h, 200);
                }
                fclose($h);

                # search need <link> tag
                if (preg_match('/<([^>]*)link([^>]*)(rel="icon"|rel="shortcut icon")([^>]*)>/iU', $html, $out))
                {

                    $link_tag = $out[0];
                    if (preg_match('/href([s]*)=([s]*)"([^"]*)"/iU', $link_tag, $out))
                    {
                        $this->ico_type = (!(strpos($link_tag, 'png')===false)) ? 'png' : 'ico';
                        $ico_href = trim($out[3]);
                        if (strpos($ico_href, 'http://')===false)
                        {
                            $ico_href = rtrim($this->site_url, '/') . '/' . ltrim($ico_href, '/');
                        }
                        $this->ico_url = $ico_href;
                    }
                }
            }           
        }
        return $this->ico_url;
    }

    # check that favicon is exists
    function is_ico_exists()
    {
        if ($this->ico_exists=='not checked')
        {
            $h = @fopen($this->ico_url, 'r');
            $this->ico_exists = ($h) ? true : false;
            if ($h) fclose($h);
        }
        return $this->ico_exists;
    }

    # get ico data
    function get_ico_data()
    {
        if ($this->ico_data=='' && $this->ico_url!='' && $this->ico_exists && !$this->is_not_modified)
        {
            # get ico data
            $ico_z = parse_url($this->ico_url);
            $ico_path = $ico_z['path'];
            $ico_host = $ico_z['host'];
            $ico_ims = gmdate('D, d M Y H:i:s', $this->if_modified_since) . ' GMT';

            $fp = @fsockopen($ico_host, 80);
            if ($fp)
            {
                $out = "GET {$ico_path} HTTP/1.1rn";
                $out .= "Host: {$ico_host}rn";
                $out .= "User-Agent=Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3rn";
                $out .= "If-Modified-Since: {$ico_ims}rn";
                $out .= "Connection: Closernrn";
                fwrite($fp, $out);
                $data = '';
                while (!feof($fp)) $data .= fgets($fp, 128);
                fclose($fp);
                $response = substr($data, 0, 15);
                $this->is_not_modified = !(strpos($response, '304')===false);
                if ($this->is_not_modified)
                {
                    $this->ico_data = 'not modified';                   
                }
                else
                {
                    $data = explode("rnrn", $data);
                    if (count($data)>0)
                    {
                        unset($data[0]);
                        $this->ico_data = implode("rnrn", $data);
                    }
                }
                $this->output_data = '';
            }
        }
        return $this->ico_data;
    }

    # get output data
    function get_output_data()
    {
        if ($this->output_data=='')
        {
            if ($this->ico_data=='not modified')
            {
                # icon is not modified since defined time
                $this->output_data = 'not modified';
            }
            elseif($this->ico_data=='')
            {
                # error(s) in getting icon data
                $this->output_data = $this->empty_png();
            }
            else
            {
                # convert ico to png, gif & return
                if (substr($this->ico_data, 0, 3)==='GIF') $this->ico_type = 'gif';
                $this->output_data = $this->ico_data;
                if ($this->ico_type==='ico')
                {
                    $this->output_data = $this->ico2png($this->output_data);
                }
                if ($this->ico_type==='gif')
                {
                    $this->output_data = $this->gif2png($this->output_data);
                }
            }
        }
        return $this->output_data;
    }   

    # if error or icon is not found we output empty png image
    function empty_png()
    {
        $res = '';
        $im = imagecreatetruecolor(16, 16);
        $color = imagecolorallocate($im, 255, 255, 255);
        imagefill($im, 1, 1, $color);

        # output png
        ob_start();

        # imagesavealpha($im, true);
        imagepng($im);
        imagedestroy($im);
        $res = ob_get_clean();
        return $res;   
    }

    # Convert gif to png function,
    # support gif-functions by GD is needed
    function gif2png($gif)
    {
        $im2 = imagecreatefromstring($gif);
   
        # background alpha is disabled because IE 5.5 + have bug with alpha-channels
        # by default background color is white
        # imagealphablending($im, false);
        # imagefilledrectangle($im, 0, 0, 16, 16, $color);
        # imagealphablending($im, true);
        $im = imagecreatetruecolor(16, 16);
        $color = imagecolorallocate($im, 255, 255, 255);
        imagefill($im, 1, 1, $color);
        imagecopy($im, $im2, 0, 0, 0, 0, 16, 16);       

        # output png
        ob_start();
        # imagesavealpha($im, true);
        imagepng($im);
        imagedestroy($im);
        imagedestroy($im2);
        $res = ob_get_clean();
        return $res;
    }

    # Convert ico to png function,
    # information about ico format is accessible on a site http://kainsk.tomsk.ru/g2003/sys26/oswin.htm,
    function ico2png($ico)
    {
        $res = '';

        while(!isset($tmp))
        {
            $tmp = '';

            # get ICONDIR struct & check that it is correct ico format
            $icondir = unpack('sidReserved/sidType/sidCount', substr($ico, 0, 6));
            if ($icondir['idReserved']!=0 || $icondir['idType']!=1 || $icondir['idCount']<1) break;
            $icondir['idEntries'] = array();
            $entry = array();
            for($i=0; $i<$icondir['idCount']; $i++)
            {
                $entry = unpack('CbWidth/CbHeight/CbColorCount/CbReserved/swPlanes/swBitCount/LdwBytesInRes/LdwImageOffset', substr($ico, 6 + $i*16, 16));
                $icondir['idEntries'][] = $entry;
            }
           
            # select need icon & get it raw data
            $iconres = '';
            $bpx = 1; # bits per pixel
            $idx = 0; # index of need icon
            foreach($icondir['idEntries'] as $k=>$entry)
            {
                if ($entry['bWidth']==16 && isset($entry['swBitCount']) && $entry['swBitCount']>$bpx && $entry['swBitCount']<33)
                {
                    $idx = $k;
                    $bpx = $entry['swBitCount'];
                }
            }           
            $iconres = substr($ico, $icondir['idEntries'][$idx]['dwImageOffset'], $icondir['idEntries'][$idx]['dwBytesInRes']);
            unset($ico);
            unset($icondir);

            # getting bitmap info
            $bitmap_info = array();
            $bitmap_info['header'] = unpack('LbiSize/LbiWidth/LbiHeight/SbiPlanes/SbiBitCount/LbiCompression/LbiSizeImage/LbiXPelsPerMeter/LbiYPelsPerMeter/LbiClrUsed/LbiClrImportant', substr($iconres, 0, 40));

            $bitmap_info['header']['biHeight'] = $bitmap_info['header']['biHeight'] / 2;           
            $number_color = 0;

            if ($bitmap_info['header']['biBitCount'] > 16)
            {
                $number_color = 0;
                $sizecolor = $bitmap_info['header']['biWidth']*$bitmap_info['header']['biBitCount'] * $bitmap_info['header']['biHeight'] / 8;  
            }
            elseif ( $bitmap_info['header']['biBitCount'] < 16)
            {
                $number_color = (int) pow(2, $bitmap_info['header']['biBitCount']);
                $sizecolor = $bitmap_info['header']['biWidth']*$bitmap_info['header']['biBitCount'] * $bitmap_info['header']['biHeight'] / 8;  
                if ($bitmap_info['header']['biBitCount']=='1') $sizecolor = $sizecolor * 2;
            }
            else return $res;

            $rgb_table_size =  4 * $number_color;       
            for($i=0; $i<$number_color; $i++)
            {
                $bitmap_info['colors'][] = unpack('CrgbBlue/CrgbGreen/CrgbRed/CrgbReserved', substr($iconres, 40 + $i*4, 4));
            }
            $current_offset = 40 + $number_color * 4;

            $arraycolor = array();

            for($i=0; $i<$sizecolor; $i++)
            {
                $value = unpack('Cvalue', substr($iconres, $current_offset, 1));
                $arraycolor[] = $value['value'];
                $current_offset++;
            }

            # background alpha is disabled because IE 5.5 + have bug with alpha-channels
            # by default background color is white
            # imagealphablending($im, false);
            # imagefilledrectangle($im, 0, 0, 16, 16, $color);
            # imagealphablending($im, true);
            $im = imagecreatetruecolor(16, 16);
            $color = imagecolorallocate($im, 255, 255, 255);
            imagefill($im, 1, 1, $color);

            # getting mask
            $alpha = '';
            for($i=0; $i<16; $i++)
            {
                $z = unpack('Cx/Cy', substr($iconres, $current_offset, 2));
                $z = str_pad(decbin($z['x']), 8, '0', STR_PAD_RIGHT)  . str_pad(decbin($z['y']), 8, '0', STR_PAD_LEFT);
                $alpha .= $z;
                $current_offset = $current_offset + 4;
            }

            # drawing image
            $ico_size = 16;   
            $off = 0; # range (0-255)

            # cases for different color depth
            switch ($bitmap_info['header']['biBitCount'])   
            {       

                ###################### for 32 bit icons ######################
                case 32:
                    for($y=0; $y<$ico_size; $y++)
                    {
                        for($x=0; $x<$ico_size; $x++)
                        {
                            $a = round((255-$arraycolor[$off*4+3])/2);
                            $a = ($a<0) ? 0 : $a;
                            $a = ($a>127) ? 127 : $a;
                            $color = imagecolorallocatealpha($im, $arraycolor[$off*4+2], $arraycolor[$off*4+1], $arraycolor[$off*4], $a);
                            imagesetpixel($im, $x, $ico_size-1-$y, $color);
                            $off++;
                        }
                    }
                break;

                ###################### for 24 bit icons ######################
                case 24:
                    for($y=0; $y<$ico_size; $y++)
                    {
                        for($x=0; $x<$ico_size; $x++)
                        {
                            $valpha = ($alpha[$off]=='1') ? 127 : 0;
                            $color = imagecolorallocatealpha($im, $arraycolor[$off*3+2], $arraycolor[$off*3+1], $arraycolor[$off*3], $valpha);
                            imagesetpixel ($im, $x, $ico_size-1-$y, $color);
                            $off++;
                        }
                    }
                break;

                ###################### for 08 bit icons ######################
                case 8:
                    for($y=0; $y<$ico_size; $y++)
                    {
                        for($x=0; $x<$ico_size; $x++)
                        {
                            $valpha = ($alpha[$off]=='1') ? 127 : 0;
                            $c = $arraycolor[$off];
                            $c = $bitmap_info['colors'][$c];
                            $color = imagecolorallocatealpha($im, $c['rgbRed'], $c['rgbGreen'], $c['rgbBlue'], $valpha);
                            imagesetpixel ($im, $x, $ico_size-1-$y, $color);
                            $off++;
                        }
                    }
                break;

                ###################### for 04 bit icons ######################
                # 318 = 22 (header) + 40 (bitmap_info) + 16 * 4 (colors) + 128 (pixels) + 64 (mask)
                case 4:
                    for($y=0; $y<$ico_size; $y++)
                    {
                        for($x=0; $x<$ico_size; $x++)
                        {
                            $valpha = ($alpha[$off]=='1') ? 127 : 0;
                            $c = ($arraycolor[floor($off/2)]);
                            $c = str_pad(decbin($c), 8, '0', STR_PAD_LEFT);
                            $m =  (fmod($off+1, 2)==0) ? 1 : 0;
                            $c = bindec(substr($c, $m*4, 4));
                            $c = $bitmap_info['colors'][$c];
                            $color = imagecolorallocatealpha($im, $c['rgbRed'], $c['rgbGreen'], $c['rgbBlue'], $valpha);
                            imagesetpixel ($im, $x, $ico_size-1-$y, $color);
                            $off++;
                        }
                    }
                break;

                ###################### for 01 bit icons ######################
                # 198 = 22 (header) + 40 (bitmap_info) + 2 * 4 (colors) + 64 (pixels, but real 32 needed?) + 64 (mask)
                case 1:
                    for($y=0; $y<$ico_size; $y++)
                    {
                        for($x=0; $x<$ico_size; $x++)
                        {
                            $valpha = ($alpha[$off]=='1') ? 127 : 0;
                            $c = ($arraycolor[floor($off/8)]); # меняем байт каждые 8 пикселей
                            $c = str_pad(decbin($c), 8, '0', STR_PAD_LEFT);
                            $m = fmod($off+8, 8) + 1; # bit number
                            $c = (int) substr($c, $m-1, 1);
                            $c = $bitmap_info['colors'][$c];
                            $color = imagecolorallocatealpha($im, $c['rgbRed'], $c['rgbGreen'], $c['rgbBlue'], $valpha);
                            imagesetpixel ($im, $x, $ico_size-1-$y, $color);
                            $off++;
                        }
                        $off = $off + 16;
                    }           
                break;

                ##############################################################

                default:
                return '';
            }

            # output png
            ob_start();
            # imagesavealpha($im, true);
            imagepng($im);
            imagedestroy($im);
            $res = ob_get_clean();
        }
        return $res;
    }
}

?>

Example of usage:

<?php
    require_once('favicon.inc.php');

    $favicon = new favicon('http://www.controlstyle.ru/', 0);
    $fv = $favicon->get_output_data();

    if ($fv!=='')
    {
        header('Content-type: image/png');
        echo $fv;
    }
?>

For described PHP class to work good it is required:

  • PHP language, version 4.3.2 or higher.
  • GD library (version 2.0.1 or higher, usually included in PHP by default) supports images in formats GIF and PNG.
  • A little patience and a hyperlink to this article :-).

← To publications list

Nikolay I. Yarovoy,
04/30/07.
Back to top© 2007 ControlStyle, web site development. All rights reserved.
Web site promotion and advertising.