Práce s obrázky, jejich cachování

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
aGr
Člen | 45
+
0
-

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

  1. 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é.
  2. 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
+
0
-

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)

aGr
Člen | 45
+
0
-

Díky. Obrázky tedy zmenším a uložím jinam. Akorát budu muset kontrolovat, jestli se zdrojový obrázek nezměnil a celkově zajistit tu kontrolu zda-li je to cachnuté. Tak nějak jsem si myslel, že už by to mohl framework opatřit. Možná se zkusím se podívat po nějakém „pluginu“.

hAssassin
Člen | 293
+
0
-

@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
+
0
-

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
+
0
-

@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
+
0
-

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
+
0
-

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
+
0
-

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.

na1k
Člen | 288
+
0
-

pilec, to by hrozilo snad jen v případě, kdy by byla složka s obrázky a v ní .htaccess co by při neexistující miniatuře přesměrovala na skript. Pokud je ale možné helper volat jen z šablony, je to bezpečné.

aGr
Člen | 45
+
0
-

Díky za názory, ještě to promyslím, ale asi se mi nejvíce líbí řešení od na1k.

aGr
Člen | 45
+
0
-

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.

aGr
Člen | 45
+
0
-

Stačilo přidat callback:

$template->registerHelper('getThumbnail', callback($this, 'getThumbnail'));
daliborcaja
Člen | 57
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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)

voda
Člen | 561
+
0
-

daliborcaja napsal(a):
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…

To není potřeba, pokud náhled existuje, tak ho obslouží Apache, jinak se spustí Nette a náhled vygeneruje.

daliborcaja
Člen | 57
+
0
-

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
+
0
-

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
+
0
-

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