Zpracování ikon

Školení, která pořádám

Dopsal jsem knihu

Do diskusí na těchto stránkách jsem doplnil možnost zadat adresu vlastních webových stránek. To umožňuje lépe identifikovat autora příspěvku. Pro zpestření vzhledu diskuse a pro rychlejší orientaci v ní se navíc zobrazuje u autora ikona jeho webu. Není to gravatar (které nemám moc rád), ale je to soubor favicon.ico z kořenového adresáře autorova webu (nenahlíží se tedy do stránky na hodnotu <meta> značky shortcut icon).

Se zobrazením ikon je problém v tom, že prohlížeče (přinejmenším IE6) je sice dokáží zobrazit u adresy stránky nebo v oblíbených položkách, ale nedokáží to přímo v kódu stránky. Proto je nutné ikony převést do nějakého běžného formátu. K tomu můžeme použít buď existující knihovny nebo se podívat, jak formát vypadá a napsat si zpracování sami.

Já jsem zvolil druhý případ a vytvořil následující dvě funkce. Funkce imagecreatefromico vezme název souboru s ikonou a požadované rozměry vráceného obrázku a pokusí se v souboru s ikonami najít ikonu daného rozměru s maximálním možným počtem barev. Když se jí to nepovede, tak vezme největší dostupnou ikonu, kterou na požadované rozměry následně převzorkuje.

<?php
/** Načtení obrázku z ikony, pokud není k dispozici požadovaný rozměr ikony, tak se na něj převede největší ikona
* @param string název souboru
* @param int požadovaná šířka ikony
* @param int požadovaná výška ikony
* @return resource plnobarevný obrázek s ikonou požadovaných rozměrů nebo false v případě chyby
* @copyright Jakub Vrána, http://php.vrana.cz/
*/
function imagecreatefromico($filename, $width, $height) {
    $file = file_get_contents($filename);
    if (!$file) {
        return false;
    }
    $data = unpack("vcount", substr($file, 4, 2));
    $read = array();
    for ($i=0; $i < $data["count"]; $i++) {
        $icon = unpack("Cwidth/Cheight/Ccolors/Creserved/vplanes/vbitcount/Vlength/Voffset", substr($file, 6+$i*16, 16));
        if (!$icon || !($icon2 = unpack("vibitcount/Vcompression", substr($file, $icon["offset"] + 14, 6)))) {
            return false;
        }
        if (!$read || ($read["width"] == $width && $read["height"] == $height
        ? $icon["width"] == $width && $icon["height"] == $height && $icon2["ibitcount"] > $read["ibitcount"]
        : $icon["width"] >= $read["width"] && $icon["height"] >= $read["height"] && $icon2["ibitcount"] >= $read["ibitcount"]
        )) {
            $read = $icon + $icon2;
        }
    }
    $im = icon_read(substr($file, $read["offset"] + 40, $read["length"]), $read["width"], $read["height"], $read["ibitcount"], $read["compression"]);
    if ($read["width"] == $width && $read["height"] == $height) {
        return $im;
    }
    $im2 = imagecreatetruecolor($width, $height);
    imagecopyresampled($im2, $im, 0, 0, 0, 0, $width, $height, imagesx($im), imagesy($im));
    return $im2;
}
?>

Pro pohodlné získávání hodnot ze souboru se používá funkce unpack, pro vlastní načtení dat potom uživatelská funkce icon_read:

<?php
/** Načtení obrazových dat ikony
* @param string obrazová data
* @param int šířka obrázku
* @param int výška obrázku
* @param int počet bitů na pixel
* @param int komprese dat - ignoruje se
* @return resource plnobarevný obrázek s ikonou
* @copyright Jakub Vrána, http://php.vrana.cz/
*/
function icon_read($file, $width, $height, $bitcount, $compression = 0) {
    $im = imagecreatetruecolor($width, $height);
    imagefilledrectangle($im, 0, 0, $width-1, $height-1, imagecolorallocate($im, 255, 255, 255)); // mohla by se používat transparentnost
    if ($bitcount <= 8) {
        $colors = array();
        for ($i=0; $i < pow(2, $bitcount); $i++) {
            $color = unpack("Cblue/Cgreen/Cred", substr($file, 4*$i, 3));
            $colors[] = imagecolorallocate($im, $color["red"], $color["green"], $color["blue"]);
        }
        $file = substr($file, 4*pow(2, $bitcount));
    }
    for ($y=0; $y < $height; $y++) {
        for ($x=0; $x < $width; $x++) {
            $offset = 32*$y*ceil($width*$bitcount/32) + $x*$bitcount;
            $trans = ord($file[4*$height*ceil($width*$bitcount/32) + 4*$y*ceil($width/32) + floor($x/8)]) & (1 << (7 - $x % 8));
            if ($bitcount <= 8) {
                $byte = ord($file[floor($offset/8)]);
                $color = $colors[$byte >> (8 - $bitcount - $offset % 8) & (pow(2, $bitcount) - 1)];
            } elseif ($bitcount == 16) {
                $colors = unpack("nbgr", substr($file, $offset/8, 2));
                $color = imagecolorallocate($im, round(255/31 * ($colors["bgr"] & 31)), round(255/63 * (($colors["bgr"] >> 5) & 63)), round(255/31 * ($colors["bgr"] >> 11)));
            } elseif ($bitcount == 32) {
                $colors = unpack("Cblue/Cgreen/Cred/Calpha", substr($file, $offset/8, 4));
                $color = imagecolorallocate($im, 255 - $colors["alpha"] + round($colors["red"] * $colors["alpha"] / 255), 255 - $colors["alpha"] + round($colors["green"] * $colors["alpha"] / 255), 255 - $colors["alpha"] + round($colors["blue"] * $colors["alpha"] / 255));
            } else {
                $colors = unpack("Cblue/Cgreen/Cred", substr($file, $offset/8, 3));
                $color = imagecolorallocate($im, $colors["red"], $colors["green"], $colors["blue"]);
            }
            if (!$trans) { // $background[0] ^ $colors["red"] prohlížeče nepoužívají
                imagesetpixel($im, $x, $height - $y - 1, $color);
            }
        }
    }
    return $im;
}
?>

Funkce převezme vlastní obrazová data a informace o nich a pomocí funkce imagesetpixel je umístní do obrázku. Pro získání informací o transparentnosti a indexech barev u paletových obrázků se používají bitové operace.

Pro průhledné body by se dala používat průhledná barva, ta je ale u plnobarevných PNG obrázků problematická. Navíc se obrázek může převzorkovat, při čemž se informace o průhlednosti ztratí. Proto se pro pozadí použije jednoduše bílá barva.

Obrázek vrácený funkcí imagecreatefromico můžeme třeba funkcí imagepng uložit do souboru nebo poslat prohlížeči.

Implementace na PHP triky

Implementace na těchto stránkách vypadá tak, že po přidání diskusního příspěvku obsahujícího URL se stáhne ikona a uloží se do cache. Pokud ikona neexistuje, uloží se do cache prázdný soubor. Pokud už soubor v cache uložen je, tak se ikona znovu nestahuje. Při vypisování diskusních příspěvků se odkazy na autorovy stránky (na rozdíl od odkazů přímo v příspěvku) vypisují s atributem rel="nofollow", aby nemátly vyhledávače.

Na závěr přináším seznam deseti nejaktivnějších diskutérů na těchto stránkách:

AutorPočet příspěvků
Jakub Vrána609
dgx167
spaze108
llook64
Jakub Podhorský51
Lukas50
Leo42
Michal31
@ss@ssIn31
Llaik28
Celkem3258

U nejaktivnějších snadno identifikovatelných autorů jsem URL zpětně doplnil. Pokud jste v minulosti na těchto stránkách aktivně diskutovali a máte zájem o totéž, zanechte komentář u tohoto článku.

Jakub Vrána, Seznámení s oblastí, 21.2.2007, diskuse: 58 (nové: 0)

Diskuse

ikona MiSHAK:

HH pěkné jak je to s licencí?
# 21.2.2007 02:02:31 reagovat

ikona Jakub Vrána:

Stejně jako s ostatními skripty na těchto stránkách, je to uvedeno v patičce: "Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení", http://creativecommons.org/licenses/by/2.5/.
# 21.2.2007 08:57:54 reagovat

ikona arieslink:

Have been looking for it for so long!
Nice!
# 24.8.2009 21:30:57 reagovat

Michal:

Skoro to vypadá, že si v kometářích povídáš sám pro sebe :)
# 21.2.2007 02:47:49 reagovat

ikona Fruiko:

Já jen zkouším, jak to zobrazí ikonku... :-)

A jinak proč ne GRAVATARy? Ikonku by měli i ti, co nemají web.
# 21.2.2007 08:32:37 reagovat

ikona Jakub Vrána:

Takhle mají ikonu i ti, co nemají e-mail :-). Teď vážně - Gravatary jsou vázané na e-mail a ten já nerad někde uvádím.
# 21.2.2007 08:59:51 reagovat

ikona JOKER:

Pěknééé, moc pěknééé.
# 21.2.2007 09:03:34 reagovat

ikona Arcao:

Pekne...
# 21.2.2007 09:11:39 reagovat

ikona Arcao:

Hm, ale moji ikonku to moc nesezralo
# 21.2.2007 09:12:09 reagovat

ikona Jakub Vrána:

Díky za upozornění, u 32-bitových ikon se nepočítalo s alfa-kanálem.
# 21.2.2007 11:10:55 reagovat

ikona TPY:

zajimava myslenka :-)
# 21.2.2007 09:44:56 reagovat

ikona Agarwaen:

Dobry napad! Uz jsem videl kdeco, ale tohle mne skutecne zaujalo! :)
# 21.2.2007 10:54:01 reagovat

Mazlo:

Test ikonky :-) Jinak fakt super nápad.
# 21.2.2007 11:05:28 reagovat

ikona Jakub Vrána:

http://www.mazlo.org/favicon.ico není ikona, ale BMP obrázek.
# 21.2.2007 11:09:48 reagovat

ikona Vojtěch Semecký:

Zkoušel jsem kdysi uvedené ICO knihovny (class.ico.php a ImageCreateFromICO) a obě na určitých ikonách kolabovali. Tady koukám, že je ten samý problém (viz ikona uživatele Arcao). Nevíte v čem by mohl být problém?
# 21.2.2007 11:06:20 reagovat

ikona Vojtěch Semecký:

U uživatele Mazlo to taky vrací jen černý čtvereček. U mého Kratce.cz to sice ikonu načte, ale z nějakých důvodů je rozsypaná :-( Schválně, co to udělá se SrovnaniCen.cz.
# 21.2.2007 11:07:37 reagovat

ikona Jakub Vrána:

Rozsypanost už jsem opravil, špatně se zpracovávaly dvoubarevné ikony rozměru 16x16. Problém byl v tom, že řádky v bitmapách musí být zarovnané na 4 bajty. V obrazových datech a transparentnosti se s tím počítalo, ve výpočtu začátku transparentních dat ne.
# 21.2.2007 12:03:08 reagovat

ikona Vojtěch Semecký:

Děkuji za rychlou reakci. Tím je to asi dotaženo k dokonalosti. Gratuluji!
# 21.2.2007 12:24:58 reagovat

ikona Vojtěch Semecký:

Tak SrovnáníCen.cz má ikonu v pohodě. To nechápu, vyráběl jsem je stejným způsobem.
# 21.2.2007 11:08:21 reagovat

ikona Petr Vaclavek:

Super napad! Ja uz o tom premyslel drive, prece jen krom gravataru, ktery je vazany na email, existuji avatary a jine xxxtary, ktere jsou propojene s webem, ale bohuzel nejsou moc rozsirene. Pouzit favikonu me nikdy nenapadlo. Ted uz mi chybi ke spokojenosti jen plugin pro Textpattern, ktery by to umel pekne zpracovat :)
# 21.2.2007 11:16:42 reagovat

Jakub Podhorský:

pěknej nápad...tohle se mi hodně líbí

ani jsem netušil že jsem takovej "diskutér" :)
# 21.2.2007 14:05:57 reagovat

ikona Dan:

Test ikonky
# 21.2.2007 16:11:20 reagovat

ikona Honza:

Hm a co moje výtečné png ikonky?
# 21.2.2007 18:34:43 reagovat

ikona Kevujin:

Tak já to teda taky zkusím .)
# 21.2.2007 19:02:46 reagovat

ikona p360t:

No, keď sme pri tom skúšaní... Inak, aký je asi najvhodnejší formát faviconky? Ja používam staré 16*16 ICO, ale ako tak pozerám, niekto sa s tým viac hrá. Je lepšia jednoduchosť a krátky kód alebo grafická lahôdka s veľkým dátovým objemom?
# 21.2.2007 19:57:36 reagovat

ikona TZ:

taky testík
# 21.2.2007 20:34:04 reagovat

ikona Jasper:

test
# 28.2.2007 00:32:27 reagovat

Pavel:

Gratuluji, zajimavé zpestření
# 21.2.2007 21:37:00 reagovat

Pavel:

tak jsem ji dal do rootu, snad to napodruhe projde
# 22.2.2007 23:35:03 reagovat

ikona elfineer:

Taky jen zkousim ikonku :). Ale bezva napad.
# 21.2.2007 21:56:11 reagovat

ikona D1ce:

Zírám, co kousek kódu to skvost, sice to nemohu objektivně posoudit, jelikož dokonale nerozumím kódu, ale stejně mě to ohromilo. Kdybych se o něco podobného pokoušel sám, asi by vznikla třída na více než 1000 řádky a fungovala by jen pokud by vítr vál z té správné strany(jestli vůbec).
# 22.2.2007 00:12:59 reagovat

ikona Skaven:

Tak ono to opravdu funguje! Opravdu pěkné...
# 22.2.2007 00:46:05 reagovat

Salko:

Tak isto iba skúška
# 22.2.2007 20:20:00 reagovat

ikona Jakub Vrána:

Jak jsem psal - ikona se bere z rootu.
# 22.2.2007 22:25:29 reagovat

mark:

možná by to přeci jen chtělo trochu zobecnit, a alespoň na tu metainformaci v HTML se jednou za čas podívat ...
# 25.2.2007 10:31:15 reagovat

ikona Jakub Vrána:

Do rootu na favicon.ico se kouká kde kdo (jestli tam tento soubor nemáte, tak se schválně někdy podívejte do errorlogu), takže je stejně lepší mít ikonu právě tam. Proto jsem se parsováním HTML nezabýval.
# 25.2.2007 13:59:38 reagovat

ikona balud:

Test a gratulace k zajímavému nápadu... ostatně Gravatar se nějak těřkopádně probouzí ze zimního spánku.... tak třeba tudy vede cesta. Zas ne každej má vlastní web... no
# 22.2.2007 20:26:26 reagovat

ikona Pilda:

Super nápad. Ani bych nečekal, že tak zdánlivě jednoduchá věc potřebuje tak dlouhý script :)
# 25.2.2007 00:16:25 reagovat

Pilda:

<citace>(na rozdíl od odkazů přímo v příspěvku) vypisují s atributem rel="nofollow", aby nemátly vyhledávače.</citace>

Jak to mysliš, "aby nemátly"?
# 3.3.2007 01:22:45 reagovat

ikona Jakub Vrána:

Vyhledávače si o odkazech myslí (alespoň teoreticky), že vedou na relevantní stránky. Relevance u odkazu na autora příspěvku je jen velmi volná, proto o tom vyhledávač takto informuji.
# 3.3.2007 01:34:34 reagovat

ikona 3rojka:

Já chi taky to zkusit.
# 4.3.2007 12:01:44 reagovat

ikona 3rojka:

No tak nějak mi to nefunguje.
# 4.3.2007 12:03:28 reagovat

ikona 3rojka:

No tak že by to bylo tím BMP formátem
# 4.3.2007 12:06:32 reagovat

ikona 3rojka:

No tak pořád nic?
# 4.3.2007 12:07:19 reagovat

ikona 3rojka:

No tak teda nevím, sorry za vygenerování tolika bez účelných komenářů.
# 4.3.2007 12:09:24 reagovat

ikona Jakub Vrána:

Jakmile se ikona jednou načte (byť neplatná nebo neexistující), tak se uloží do cache a příště se už nenačítá. V cache jsem ji ručně obnovil.
# 5.3.2007 11:21:59 reagovat

ikona test:

test ikony
# 7.5.2007 21:25:50 reagovat

Jirka:

Není mi jasné, co za proměnnou musím vypsat, abych dostal ikonu? Zkoušel jsem:

<pre><img src="<?php echo $file; ?>" width="<?php echo $width; ?>" height="<?php echo $height; ?>" /></pre>

ale tohle je na moje php schopnosti přespříliš složité :-(
# 28.5.2007 01:17:00 reagovat

ikona Jakub Vrána:

Načtenou ikonu lze buď uložit do souboru - třeba funkcí imagepng, nebo ji lze téže funkcí poslat do prohlížeče a tento skript zavolat:

<img src="ikona.php?url=...">

ikona.php:
<?php
header
("Content-Type: image/png");
imagepng(imagecreatefromico($_GET["url"], 16, 16));
?>
# 28.5.2007 12:07:44 reagovat

ikona Marty:

test ikonky :-)
# 27.7.2007 15:37:01 reagovat

ikona Jakub Vrána:

Ikona http://www.martinvseticka.eu/favicon.ico není ve formátu ICO, ale ve formátu BMP.
# 27.7.2007 17:30:05 reagovat

ikona Marty:

Opraveno, snad je to uz spravne.
# 27.7.2007 18:06:30 reagovat

ikona Mark V:

Sorry, I don't really understand/write your language... Understanding PHP is enough for the article thou ;). Great code, cheers! I own you a beer for easing my multi-hour pain in the ass trying to figure out the very same thing.
# 26.11.2007 01:03:44 reagovat

ikona Mark V:

In case anyone's interested...

http://printf.ru/wiki/Favicon::fetch

Here's the PHP code needed to fetch the favicon from a given site.
Parses (well, kindof) <meta> section, and uses /favicon.ico as a fallback. Usage is as easy as

header('Content-Type: image/png');
$image = Favicon::fetch('php.vrana.cz');
if ($image) imagepng($image);

Same attribution license as the original code.
# 28.11.2007 00:51:46 reagovat

ikona xsuchy09:

Prosím o opravu ikony (smazání neplatné v cache) :) díky
# 10.3.2009 05:02:47 reagovat

ikona Jakub Vrána:

Obnoveno.
# 10.3.2009 14:51:00 reagovat

mutty:


._
|\
  \
   je tam?
# 11.4.2010 18:01:33 reagovat

ikona Jakub Vrána:

Není, protože soubor favicon.ico na serveru neexistuje.
# 12.4.2010 14:05:05 reagovat

Vložit příspěvek

Používejte diakritiku. Nelze používat HTML značky, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

© 2005-2010 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.