Image helper – lazy generování náhledů

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

Ahoj,
vytvořil jsem první nástřel, jak bych si představoval „lazy“ generování náhledů na webu. Vešlo se mi to do jedné metody, tak posuďte :)

Chtěl jsem to udělat trochu univerzálnější, ale kvůli tomu, jak Nette parsuje parametry helperů (obalí je uvozovkami, takže je nelze převzít jako instanci objektu) se mi to nepodařilo. Ty části jsou tedy zatím zakomentované. Odkomentoval jsem to, ty Html objekty můžete tvořit v Presenteru.

Taky jsem zakomentoval dvě proměnné s cestami, které jsem si vytvořil, ale pak zjistil, že je nepotřebuji :)
A taky doufám, že detekce všech cest (je jich hodně) je spolehlivá.

Při neexistenci souboru s obrázkem vracím string, že neexistuje (nechci shodit celou aplikaci kvůli chybějícímu obrázku, proto nevyhazuji exception), ale pokud neexistuje složka, do které se má zapsat náhled, tak už Exception klidně vyhodím, to si měl admin zařídit :))

Dále jsem váhal, jestli poslední parametr nemá být boolean $needLink, ale to by omezilo univerzálnost a dám tam tedy Html $wrapper, přičemž pak budu detekovat, jestli se jedná o odkaz a pokud ano a zároveň neobsahuje atribut href, doplním ho cestou k originálnímu (velkému obrázku).

Použití

{* Cesta k obrázku musí být absolutní, parametry metody jsou: width, height, alt *}
{=WWW_DIR . $baseUri . 'images/image.jpg' |image:100,50,'Trance cézet'}

Zdrojový kód

	public static function image($absolutePath, $width, $height, $alt='', Html $img=NULL, Html $wrapper=NULL) {
		if (!file_exists($absolutePath)) {
			return 'Image not found.';
		}

		$absoluteDirPath = dirname($absolutePath);

		$filename = basename($absolutePath);
		$explodedFilename = explode('.', $filename);
		$extension = $explodedFilename[count($explodedFilename)-1];
		$basename = substr($filename, 0, -strlen($extension)-1);

		$thumbnailFilename = $basename . '-thumb-' . $width . '-' . $height . '-' . filemtime($absolutePath). '.' . $extension;
		$absoluteThumbnailDirPath = $absoluteDirPath . '/thumbnails';
		$absoluteThumbnailPath = $absoluteThumbnailDirPath . '/' . $thumbnailFilename;
		$webThumbnailPath = substr($absoluteThumbnailPath, strlen(WWW_DIR));

		if (!is_dir($absoluteThumbnailDirPath) || !is_writable($absoluteThumbnailDirPath)) {
			throw new InvalidStateException('Thumbnail path ' . $absoluteThumbnailDirPath . ' does not exists or is not writable.');
		}

		if (file_exists($absoluteThumbnailPath)) {
			$image = Image::fromFile($absoluteThumbnailPath);
		} else {
			$image = Image::fromFile($absolutePath);
			$image->resize($width, $height);
			$image->save($absoluteThumbnailPath);
		}

		if ($img === NULL) {
			$img = Html::el('img');
		}

		$img->src = $webThumbnailPath;
		$img->width = $image->width;
		$img->height = $image->height;
		$img->alt = $alt;

		if ($wrapper !== NULL) {
			return $wrapper->add($img);
		}

		return $img;
	}

Editoval Ondřej Mirtes (25. 1. 2010 22:42)

Honza Kuchař
Člen | 1662
+
0
-

Jéé to je fajn. Vypadá to suprově. Proč to ještě není v extras?

Ondřej Mirtes
Člen | 1536
+
0
-

Je to spíš kus kódu než komponenta :) Zkusím to nějak obalit (extendovat Image?) a vydat :)

A taky jsem přišel během používání na bug – mám ho v jednom projektu opravenej, ale už nevím, co to bylo :)

EDIT: Bug opraven i v původním příspěvku (šlo o to, že pokud jste aktualizovali originální obrázek, tak se náhled neupdatoval, proto jsem do cesty náhledu přidal i filemtime originálu) + kontroluju zapisovatelnost složky pro náhledy :)

Asi to vydám tak, jak to je, protože s obalením nějaké třídy (a ubráním static) by bylo zase složitější to zaregistrovat jako helper, což je tady asi ta killer feature :)

Editoval Ondřej Mirtes (24. 1. 2010 23:58)

Honza Kuchař
Člen | 1662
+
0
-

Já bych nic nextendoval. A zařadil bych to sem: https://componette.org/search/?q=

rokerkony
Člen | 122
+
0
-

paráda diky honzo že si to obživil… nějak mi to v RSSce zaniklo a už to plně využívám… takže díky patří i ondrovi :) díky a šup do extras

Honza Kuchař
Člen | 1662
+
0
-

Používám na jednom webu (bez Nette) něco podobného a vím jak je to užitečné. :) Docela mě překvapilo, že to tu jen tak leží bez povšimnutí.

Michalek
Člen | 210
+
0
-

Pokud mám velký web s hodně fotkama (počítám 1.000.000+) a všude bych to dělal takhle, asi by to bylo dost náročné, ne? (neberu v úvahu cachování výsledného html)

V současnosti jsem se inspiroval řešením zmíněným v jednom příspěvku tady a pokud mi obrázek na adrese /storage/obrazek/uzel/2002/05/48283897ee46c/4b32d35f9c437/90_90_crop.jpg neexistuje, přesměruju přes .htaccess požadavek na „lazy generátor“, ten požadovaný obrázek vygeneruje dle parametrů z 90_90_crop.jpg, uloží na místo a přesměruje se zase tam.

Mám tam dva nevyřešené problémy

  • když změním 90 na 91, zůstanou mi tam zbytečně vygenerované obrázky (což tenhle lazy generátor taky neřeší)
  • když někdo napíše skript na načítání obrázků od 1_1.jpg do 1000_1000.jpg asi mi zavaří server :)

Editoval Michalek (26. 1. 2010 14:12)

rokerkony
Člen | 122
+
0
-

tak tenhle generator take nezmensuje obrazek vzdy ale jen tehdy pokud jeste neexistuje. Pokud existuje tak se jen precte. cili v podstate asi stejne reseni jak popisujes.
S temi problemy asi moc reseni neexistuje… jedine co me napada pro to prvni je obcas to smazat cronem a nechat stranku nacist znova a timm vygenerovat obrazky, ale to je zase silene narocne… :-/

Aurielle
Člen | 1281
+
0
-

Můj lazy generátor ve spojení s texy!
https://forum.texy.info/…ovac-obrazku

uestla
Backer | 796
+
0
-

Rozhodl jsem se také přidat trochu do vínka ;)

// EDIT: aktualizoval jsem kód, je tam pár drobných změn

Mám třídu LayoutHelpers a v ní podobnou metodu na vytváření miniatury – s tím rozdílem, že zbytečně nevytváří pro každý zmenšovaný obrázek instatci Image a nevrací instance Html, nýbrž cestu – tak, aby byl tag <img /> kompletně v režii šablonáře.

Kód metody:

abstract class LayoutHelpers extends Object
{
	/*************************** vytvareni miniatur ***************************/



	/** @var string relativni URI k adresari s miniaturami (zacina se v document_rootu) */
	public static $thumbDirUri = NULL;



	/**
	 * Vytvoreni miniatury obrazku a vraceni jeho URI
	 *
	 * @param  string relativni URI originalu (zacina se v document_rootu)
	 * @param  NULL|int sirka miniatury
	 * @param  NULL|int vyska miniatury
	 * @return string absolutni URI miniatury
	 */
	public static function thumb($origName, $width, $height = NULL)
	{
		$thumbDirPath = WWW_DIR . '/' . trim(self::$thumbDirUri, '/\\');
		    $origPath = WWW_DIR . '/' . $origName;

		if (($width === NULL && $height === NULL) || !is_file($origPath) || !is_dir($thumbDirPath) || !is_writable($thumbDirPath))
			return $origName;

		$thumbName = self::getThumbName($origName, $width, $height, filemtime($origPath));
		$thumbUri = trim(self::$thumbDirUri, '/\\') . '/' . $thumbName;
		$thumbPath = $thumbDirPath . '/' . $thumbName;

		// miniatura jiz existuje
		if (is_file($thumbPath)) {
			return $thumbUri;
		}

		try {

			$image = Image::fromFile($origPath);

			// zachovani pruhlednosti u PNG
			$image->alphaBlending(FALSE);
			$image->saveAlpha(TRUE);

			$origWidth = $image->getWidth();
			$origHeight = $image->getHeight();

			$image->resize($width, $height,
					$width !== NULL && $height !== NULL ? Image::STRETCH : Image::FIT)
					->sharpen();

			$newWidth = $image->getWidth();
			$newHeight = $image->getHeight();

			// doslo ke zmenseni -> ulozime miniaturu
			if ($newWidth !== $origWidth || $newHeight !== $origHeight) {

				$image->save($thumbPath);

				if (is_file($thumbPath))
					return $thumbUri;
				else
					return $origName;

			} else {
				return $origName;
			}

		} catch (Exception $e) {
			return $origName;
		}
	}



	/**
	 * Vytvori jmeno generovane miniatury
	 *
	 * @param  string relativni cesta (document_root/$relPath)
	 * @param  int sirka
	 * @param  int vyska
	 * @param  int timestamp zmeny originalu
	 * @return string
	 */
	private static function getThumbName($relPath, $width, $height, $mtime)
	{
		$sep = '.';
		$tmp = explode($sep, $relPath);
		$ext = array_pop($tmp);

		// cesta k obrazku (ale bez pripony)
		$relPath = implode($sep, $tmp);

		// pripojime rozmery a mtime
		$relPath .= $width . 'x' . $height . '-' . $mtime;

		// zahashujeme a vratime priponu
		$relPath = md5($relPath) . $sep . $ext;

		return $relPath;
	}
}

Když pak helper registruji v Presenteru, zadám také cestu k adresáři pro miniatury:

abstract class BasePresenter extends Presenter
{
	protected function beforeRender()
	{
		parent::beforeRender();

		LayoutHelpers::$thumbDirUri = 'images/thumbs';
		$this->template->registerHelper('thumb', 'LayoutHelpers::thumb');
	}
}

No a jsme u cíle – samotné použití v šabloně:

<img src="{=$basePath}/{='images/obrazek.jpg'|thumb:150}">

// EDIT: tento helper jde snadno kombinovat s Texy! (vizte vlákno na Texy! fóru)

Editoval uestla (23. 10. 2010 5:38)

Roman Ožana
Člen | 52
+
0
-

Včera jsem se trošku inspiroval a napsal svůj helper https://gist.github.com/547309

toka
Člen | 253
+
0
-

Nebylo by dobré mít i parametr, pro ovlivnění, na ukládání náhledu? A pakliže existuje, již ho znovu nevytvářet?

saimons
Člen | 293
+
0
-

Diky super vec, jen skoda, ze na to musim narazit nahodou kdyz hledam neco jineho. Bylo by fajn kdyby tyhle male moduly byly nkede po kupe.