Práce s obrázky, jejich cachování
- aGr
- Člen | 45
Zdravím, měl bych takový široký dotaz.
Zajímalo by mě jak správně bych měl pracovat s obrázky v Nette. Na
hlavní stránce mám cca 10 obrázku, jsou velké, tudíž je potřebuji
zmenšit. Mám funkci resizeImages()
kde volám na každý
Nette\Image obrázek funkci resize(něco, NULL). V šabloně pak vypisuji
pomocí
{$product[images][0]['image']->toString()|dataStream}
.
Co se mi nezdá:
- Nevím, jestli je zcela košer volání funkce v šabloně, ale to asi není problém to volat předtím a uložit do proměnné.
- Stránka se načítá příliš dlouho, tuším, že to bude tím zmenšováním. Je tedy třeba nějaký caching. Rozumím tomu dobře, že to třída Image neumožňuje a je třeba si to napsat? Neříkám, že to je bůhvíjaký problém, jen mě to překvapuje, neboť tuto záležitost musí řešit každý.
Díky za poznatky, popř. rady jak to řešíte vy.
- redhead
- Člen | 1313
Tak pokud jsou to velké obrázky, tak dataStream není moc užitečné (to se hodí pro takové ty ikonky apod.), protože to prohlížeč nemůže cachovat sám.
Lepší je si obrázky ukládat zmenšené někam do složky a použít
jednoduše src
atribut. Tím se nebudou každým požadavkem
obrázky zmenšovat a navíc si to může prohlížeč cachenout.
Editoval redhead (6. 9. 2011 12:43)
- hAssassin
- Člen | 293
@aGr > a proc si ty obrazky nezmensis a neulozis jinam (pripadne dal neupravis – watermark, apod) uz pri jejich nahrani? Prece je nebudes zmensovat porad znovu, resp. testovat jestli je aktualni verze obrazku zmensena a kdyz ne, tak se zmensi… Myslim ze pri uploadu obrazku to je idealni reseni a o cachovani se postara uz sam prohlizec (takze trochu nechapu co myslis tim „celkově zajistit tu kontrolu zda-li je to cachnuté“).
- aGr
- Člen | 45
Protože je mám na více místech, na jednom místě bych je chtěl větší než na dalším. No myslel jsem tím tu kontrolu zda-li tenhle obrázek o oné velikosti již je zmenšený, řekl jsem to blbě. Vím, že existuje nějaký (dokonce český myslím) image resizer (bohužel název nevím), kterému se za název dají pomlčky a pak parametry velikosti a zbytek obstará sám. Takže např. cesta/obrazek–50×30.jpg ořízne obrázek na 50×30 (a umí i zachování poměru atd.) a přišlo mi to opravdu elegantní. Ale dobrá buď to napíši nebo to najdu :).
- hAssassin
- Člen | 293
@aGr > jasny, chapu, ale co ti brani v tom, mit
tech velikosti vic a pri uploadu (ktery se provadi daleko min casteji nez
zobrazeni) si pripravit vsechny velikosti, ulozit je na disk (treba do slozek) a
pak uz jen tahat z dany slozky. Jako bonbonek by sis mohl ulozit i original a
pri zobrazeni jen testovat, jestli ten obrazek v dany velikosti na disku
existuje ( is_file($image)
) a pouze v tom pripade ho z toho
originalu vygenerovat znovu. Takhle nejak sme to resili v byvalym zamestnani a
celkem se mi to libilo.
- David Ďurika
- Člen | 328
resize predsa dokaze aj nette
a ako pisal hAssassin pri uploadovani (zmene) obrazku si hned vygenerujes tolko velosti kolko potrebujes napr: image_small.jpg, image_original.jpg, image_big.jpg a potom si do latte uz len spravis helper a pouzijes napr takto:
<?php
//$imageName = WWW_DIR.'/images/image.jpg';
<img src={$imageName|myImgHelper:"big" />
// vysledok:
<img src="domena.sk/images/image_big.jpg" />
?>
- na1k
- Člen | 288
Já jsem zastánce ukládání původní velikosti a lazy generování toho, co je potřeba. Dřív jsem používal složkové řešení se předdefinovanými velikostmi, ale poté co se velikosti obrázků několikrát za chodu běhu měnily, jsem od něj upustil.
Aktuálně používám helper, který vypadá asi takto
<?php
public static function getThumbnail($path, $w = NULL, $h = NULL, $q = 85) {
// better use default values than fire an exception; users dont like that
if ($w === NULL && $h === NULL)
$w = 100;
// absolute the dir
$relPath = $path;
$path = WWW_DIR . $path;
// if the source file does not exist, dont modify the path; let the browser handle broken image itself
if (!\file_exists($path))
return $relPath;
// create hash that specifies the path and the settings
$info = \pathinfo($path);
$ext = $info['extension'];
$hash = md5($path) . '_' . $w . '_' . $h . '_' . $q;
$newPath = \PUBLIC_TEMP_DIR . \DIRECTORY_SEPARATOR . $hash . '.' . $ext;
// is the file cached?
if (\file_exists($newPath)) {
return \str_replace(WWW_DIR, '', $newPath);
}
// create new file
$image = \Nette\Image::fromFile($path);
// calculate missing dimensions to keep proportions
if ($w === NULL)
$w = ($image->getWidth() / $image->getHeight()) * $h;
if ($h === NULL)
$h = ($image->getHeight() / $image->getWidth()) * $w;
// save
$image->resize($w, $h, \Nette\Image::FILL)
->crop('50%', '50%', $w, $h)
->save($newPath, $q);
// it is cached now
return \str_replace(WWW_DIR, '', $newPath);
}
?>
Určitě by se dal v mnoha směrech vylepšit, ale hlavní myšlenka je
jasná – vytvořený soubor (v tempu) ve svém názvu zahrnuje jak cestu
k původnímu souboru, tak i parametry kterými byl náhled vyroben. Vrací
vždy cestu k existující miniatuře v tempu, takže použití
klasicky <img src="{$path|getThumbnail:400:300}">
Pokud by se měly hlídat i změny v původním souboru, jen bych do názvu souboru přidal velikost (filesize) originálu.
- Jan Endel
- Člen | 1016
Jenom doplnim k tomuhle resizeru, nejak podobne jsme to resili u byvaleho zamestnavatele, jenom pozor na to aby width a height nebyly pristupne pres url, takhle utocnik si muze chtit vyzadat ten samy obrazek v rozmerech od 1×1 az treba po 1000 × 1000, cili se milionkrat bude resizovat obrazek.
- aGr
- Člen | 45
na1k napsal(a):
Aktuálně používám helper, který vypadá asi takto
Tak ještě bych potřeboval radu, jak helper zaregistrovat. Mám v BasePresenteru:
public function createTemplate($class = NULL) {
$template = parent::createTemplate($class);
$template->registerHelper('getThumbnail', 'getThumbnail');
return $template;
}
a hned za tím deklaraci tohoto helperu. V šabloně mám
src="{$i|getThumbnail:400:300}"
. Dostávám chybku:
Callback 'getThumbnail' is not callable
Díky.
- daliborcaja
- Člen | 57
Mám stejný problém, avšak chtěl bych ho řešit trochu jinak než jak je
řečeno tady. Nechci obrázky vůbec ukládat někde do uživateli dostupných
složek, chci k nim přistupavat přes php. Obrázky mám uloženy
v databázi.
Nešlo by použít cache v nette?
Pro vykreslovani miniatur obrazku pouzivam v response tento kod:
$file = Image::fromString(FileModel::getConnection()->unescape($this->file->content, dibi::BINARY));
$file->resize(150, 150);
$file->send();
Prvni dva radky kodu ale trvaji zhruba 1 sekundu, takže je načítání
miniatur celkem pomalé.
V databazi mam ulozeno i datum posledni zmeny obrazku, takze pro invalidaci by
se dalo pouzit toto.
Šlo by to nějak cachovat pomocí nette cache?
- petr.pavel
- Člen | 535
Zrovna nedávno jsem to řešil, popis najdeš tady. Cache má totiž obrovskou výhodu, že samo uklízí osiřelé/staré náhledy.
- daliborcaja
- Člen | 57
To nevypadá špatně, akorát to používá jako zdroj toho velkého
obrázku soubor a né databázi.
Bude to chtít předělat tu invalidaci – tady to kontroluje jestli se
nezměnil zdrojový soubor, já budu potřebovat místo kontrolního součtu
nebo co to používá použít datum změny uložené v db. Nicméně chce to
se podívat do dokumentace k Nette\Caching\Cache a myslím že nebude problém
to upravit.
Dálší věc co se mi tam nelíbí je použití třídy Environment, to
nahradím DI.
Pak tu zkusím hodit ňáký výsledek.
- Jan Jakeš
- Člen | 177
Souhlas s použitím cache, ale ještě to není ideální. Startovat na
každý obrázek Nette a vracet obrázek přes něj není z hlediska výkonu
zrovna ideální. Řešením je napsat implementaci IStorage
,
která bude ukládat obrázky přímo do z webu přístupné složky a nebude
ukládat hlavičky cache přímo do obrázků.
- daliborcaja
- Člen | 57
Juan napsal(a):
Řešením je napsat implementaci
IStorage
, která bude ukládat obrázky přímo do z webu přístupné složky a nebude ukládat hlavičky cache přímo do obrázků.
A jak potom řešit oprávnění pro pžístup k danému obrázku na základě přihlášení? Jak jsem výše psal nechci obrázky vůbec ukládat někde do uživateli dostupných složek.
Jo a zadruhé se mi nelíbí to že bych tímpádem musel náhledy generovat již při načítání samotné stránky galerie, což by např. za předpokladu že se na stránku vypisuje 30 fotek a každý náhled se generuje 1 sekundu trvalo načtení stránky 30sekund. Což by mě jako klienta celkem sralo, to už je lepší když se stránka načte okamžitě a pak tam přiskakují ty náhledy, a jelikož se jich více načítá zároveň tak to není zas taková hrůza, nicméně pár sekund to trvá.
Editoval daliborcaja (2. 12. 2011 17:12)
- daliborcaja
- Člen | 57
voda napsal(a):
To není potřeba, pokud náhled existuje, tak ho obslouží Apache, jinak se spustí Nette a náhled vygeneruje.
No leda že by .htaccess pri nedostupnosti obrazku presmeroval na php
script.
Nicméně pořád tu ještě máme další problém, jak to invalidovat když
někdo obrázek upraví. V tomto případě by se zobrazil starý náhled
z cache. A mazat obrázky z cache pří nahrání nových není moc reálně,
protože pro správu obrázků v db se kromě webové administrace používá
i windows aplikace, která k databázi přistupuje přes jiný server.
V tomto celkem krkolomném případě se mi opravdu jeví jako
nejideálnější ke všemu přistupovat přes php a cache invalidovat datem
poslední změny záznamu v db.
To zabezpečení aby k obrázkům nemohl přistoupit jiný uživate by se dalo vyřešit pojmenováním obrázku ňákým hashem, což by bylo dostatečné vůči mým požadavkům.
Pokud máte ještě ňáký lepší nápad tak napište, ale mě fakt nenapadá jiná možnost – kvůli té invalidaci.
- Jan Jakeš
- Člen | 177
Aha, windows aplikace… To záleží, jakým způsobem se k serveru připojuješ, jak ty obrázky taháš, atd. Ivalidace obrázků by nebyl problém, pokud by se jednalo o webovou aplikaci. Pokud potřebuješ trochu složitější oprávnění nebo ochranu, tak tam už stejně asi budeš muset PHP aplikaci nahodit.
Ještě k té invalidaci – na webu je to přece šablona, kdo sdělí prohlížeči, kde se má ptát po obrázku. To, jak generuješ cesty k obrázkům do šablony je plně v tvých rukou a to i v tom případě, že pak obrázky už servíruje přímo apache. Pokud je cesta k obrázku nějaký hash, do kterého vložíš potřebné údaje, tak ty údaje při změně vynutí vygenerování nového náhledu. Například tedy součástí hashe může být datum vytvoření originálu nebo jeho velikost, rozměry náhledu a cokoliv dalšího, co potřebuješ. Možností je tu více, ale tohle je základní princip.
V tomhle případě by se obrázky poprvé generovaly sice už při renderování šablony (ale pak už je servíruje přímo Apache a nevidím v tomhle přístupu žádný problém). Šlo by také udělat, aby se opravdu každý obrázek začal generovat, až jako odpověď na požadavek prohlížeče (pokud by neexistoval), jenže tam musí být nějak např. z jeho názvu rozeznatelné, co vlastně vygenerovat, pokud obrázek neexistuje. To přináší bezpečnostní rizika – uživatel by si mohl „říci“ o obrázek nějakých naprosto nesmyslných rozměrů, přeplnit ti cache, atd. Ale i to by šlo zabezpečit…
To spíš pro objasnění, protože v tvém případě to možná takto nepůjde (záleží, jak si tvá windows aplikace říká o obrázky).
- daliborcaja
- Člen | 57
Juan napsal(a):
Aha, windows aplikace…
Windows aplikace je aplikace která je nainstalovaná na vedlejším Windows 2008 serveru a přistupuje přímo na SQL databázi ve které jsou mimo jiné ty obrázky. Tato aplikace je určena pouze pro zaměstnance firmy, klienti přistupují přes web pouze read only.
součástí hashe může být datum vytvoření originálu nebo jeho velikost, rozměry náhledu a cokoliv dalšího, co potřebuješ.
To mi nedocvaklo jak jsem to psal :-)
Šlo by také udělat, aby se opravdu každý obrázek začal generovat, až jako odpověď na požadavek prohlížeče (pokud by neexistoval), jenže tam musí být nějak např. z jeho názvu rozeznatelné, co vlastně vygenerovat, pokud obrázek neexistuje. To přináší bezpečnostní rizika – uživatel by si mohl „říci“ o obrázek nějakých naprosto nesmyslných rozměrů, přeplnit ti cache, atd. Ale i to by šlo zabezpečit…
Na tohle mě napadá vždy při změně v db uložit do ní i nový hash a pak k němu přidávat např. _mini , _medium , _big pro určení velikosti, čímž by se zabezpečilo že by se tam nedaly dát nesmyslné rozměry. Zpětně pokud by obrazek nebyl nalezen by se regularnim vyrazem vytahl ten hash podle kterého by se to našlo v db.
záleží, jak si tvá windows aplikace říká o obrázky
windows aplikace si to řeší všechno vlastním způsobem a přistupuje jak už sem psal přímo na SQL DB a na webu je uplně nezávislá.