Zpracování ikon

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

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 <link> 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, https://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, https://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.

Na adrese http://www.google.com/s2/favicons?domain= běží webová služba pro získávání ikon ve formátu PNG.

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í?

ikona Jakub Vrána OpenID:

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/.

ikona arieslink:

Have been looking for it for so long!
Nice!

Michal:

Skoro to vypadá, že si v kometářích povídáš sám pro sebe :)

ikona Fruiko:

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

A jinak proč ne GRAVATARy? Ikonku by měli i ti, co nemají web.

ikona Jakub Vrána OpenID:

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.

ikona JOKER:

Pěknééé, moc pěknééé.

ikona Arcao:

Pekne...

ikona Arcao:

Hm, ale moji ikonku to moc nesezralo

ikona Jakub Vrána OpenID:

Díky za upozornění, u 32-bitových ikon se nepočítalo s alfa-kanálem.

ikona TPY:

zajimava myslenka :-)

ikona Agarwaen:

Dobry napad! Uz jsem videl kdeco, ale tohle mne skutecne zaujalo! :)

Mazlo:

Test ikonky :-) Jinak fakt super nápad.

ikona Jakub Vrána OpenID:

http://www.mazlo.org/favicon.ico není ikona, ale BMP obrázek.

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?

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.

ikona Jakub Vrána OpenID:

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.

ikona Vojtěch Semecký:

Děkuji za rychlou reakci. Tím je to asi dotaženo k dokonalosti. Gratuluji!

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.

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 :)

Jakub Podhorský:

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

ani jsem netušil že jsem takovej "diskutér" :)

ikona Dan:

Test ikonky

ikona Honza:

Hm a co moje výtečné png ikonky?

ikona Kevujin:

Tak já to teda taky zkusím .)

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?

ikona TZ:

taky testík

ikona Jasper:

test

Pavel:

Gratuluji, zajimavé zpestření

Pavel:

tak jsem ji dal do rootu, snad to napodruhe projde

ikona elfineer:

Taky jen zkousim ikonku :). Ale bezva napad.

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).

ikona Skaven:

Tak ono to opravdu funguje! Opravdu pěkné...

Salko:

Tak isto iba skúška

ikona Jakub Vrána OpenID:

Jak jsem psal - ikona se bere z rootu.

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 ...

ikona Jakub Vrána OpenID:

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.

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

ikona Pilda:

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

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"?

ikona Jakub Vrána OpenID:

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.

ikona 3rojka:

Já chi taky to zkusit.

ikona 3rojka:

No tak nějak mi to nefunguje.

ikona 3rojka:

No tak že by to bylo tím BMP formátem

ikona 3rojka:

No tak pořád nic?

ikona 3rojka:

No tak teda nevím, sorry za vygenerování tolika bez účelných komenářů.

ikona Jakub Vrána OpenID:

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.

ikona test:

test ikony

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é :-(

ikona Jakub Vrána OpenID:

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));
?>

ikona Marty:

test ikonky :-)

ikona Jakub Vrána OpenID:

Ikona http://www.martinvseticka.eu/favicon.ico není ve formátu ICO, ale ve formátu BMP.

ikona Marty:

Opraveno, snad je to uz spravne.

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.

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.

ikona xsuchy09:

Prosím o opravu ikony (smazání neplatné v cache) :) díky

ikona Jakub Vrána OpenID:

Obnoveno.

mutty:


._
|\
  \
   je tam?

ikona Jakub Vrána OpenID:

Není, protože soubor favicon.ico na serveru neexistuje.

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, 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:

avatar © 2005-2016 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.