Manipulace s obrázky [Nový ImageStorage]

Martk
Člen | 661
+
+4
-

Mám tu pro Vás nový Image Storage, který byl inspirován touto komponentou. Bohužel byl pro mě postupem času nedostačující, takže jsem vytvořil nový s více možnostmi.

Git

Editoval Antik (16. 1. 2016 21:57)

Martk
Člen | 661
+
+1
-

Přidán nový addon:

UploadControl:

Slouží pro jednoduchý upload a odstranění obrázku (Pomocí checkboxu + Náhled). Momentálně pouze jednoho obrázku.

Ukázka použíti zde.

Martk
Člen | 661
+
0
-

Vydána verze 1.1

Co je nového?

  • Kompletně předělaný kód.
  • Změna velikosti noimage podle neexistujícího obrázku.
  • Možnost nastavení noimage v makru (4 parameter).
  • Vytvoření vlastního image storage s namespace.
  • UploadControl: Obrázek se nahrává jenom při úspěšném odeslání formuláře.
  • UploadControl: Obrázek se odstraňuje jenom při úspěšném odeslání formuláře.
  • Odstraněno responsive makro, nyní je možnost vytvoření pomocí img makra.
  • Při přidání // do absolutního jména obrázku se obrázek vygeneruje s $baseUri místo $basePath

Editoval Antik (8. 2. 2015 17:36)

Pavel Janda
Člen | 977
+
0
-

Vytvořil jsem si jednoduchou formFactory, ve které volám $form->addImageUpload('image', 'Image') (Nebo addUploadImage, jsem trošku zmatený, v docu. se objevují obě metody).
Není však třeba ještě nějaká konfigurace?

Při opisování příkladu z Githubu mi Nette kříčí, viz: Call to undefined method Nette\Application\UI\Form::addUploadImage()

Neměl bych někde napsat něco jako:

<?php

	// Bootstrap.php

	Nette\Object::extensionMethod(
    	'Nette\Forms\Container::addImageUpload', function ($form, $name, $label = NULL, $multiple = FALSE) {
        	return $form[$name] = new WebChemistry\Images\Addons\UploadControl($label, FALSE);
    	}
	);
?>

?

Editoval Beton (9. 2. 2015 13:14)

echo
Člen | 134
+
0
-

@Beton

Nette\Application\UI\Form::extensionMethod('addImageUpload', function(){});

Editoval echo (9. 2. 2015 14:14)

David Kudera
Člen | 455
+
+1
-

@echo: myslím, že @Beton se jen ptal, jestli to tam nemá být už od základu. Chyba v jeho zápisu by být neměla, ba dokonce by měl být i lepší. Viz link. Jen teda netuším, od které verze to takto funguje

edit: a taky formulářové prvky se mají přidávat na kontejner formu a ne na form samotný, jinak nebudou v kontejnerech dostupné

Editoval David Kudera (9. 2. 2015 14:06)

Pavel Janda
Člen | 977
+
0
-

David Kudera napsal(a):

@echo: myslím, že @Beton se jen ptal, jestli to tam nemá být už od základu. Chyba v jeho zápisu by být neměla, ba dokonce by měl být i lepší. Viz link. Jen teda netuším, od které verze to takto funguje

S tou verzí si také nejsem jist.

Jojo, šlo mi jen o to, jestli se to dělá „někde“ magicky a já píšu něco blbě a jenom ze sebe dělám vola, nebo jestli autorovi utekl kousek dokumentace.

Pavel Janda
Člen | 977
+
0
-

Mám další dotaz. :)

Možná spíš report, ale dám to radši sem k diskuzi.

Pokud si nastavím namespace uploadu například na ‚avatars/‘ . uniqid(), tak extension vygeneruje například tuto cestu: avatars/54d8ae0e53b7d/avatar.png. Nicméně, macro rozšíření si s tím jen tak neporadí.

Avšak, pokud upravím cestu (odeberu 54d8ae0e53b7d/) a přesunu adresář „original“ o úroveň výš, začne extension fungovat.

Martk
Člen | 661
+
0
-

@Beton Jestli se tam objevují dvě metody, tak se omlouvám, přepsal jsem se. Správně je jenom $form->addImageUpload().

Registraci je možno provést buď jak jsi napsal nebo zavoláním:

WebChemistry\Images\Addons\UploadControl::register();

Ovšem jsem zapomněl upravit registrační metodu, takže v další verzi to bude vypadat (Doporučuji zapsat takhle, než provádět automatickou registraci):

Nette\Object::extensionMethod('Nette\Forms\Container::addImageUpload', function ($form, $name, $label = NULL, $namespace = NULL, $defaultValue = NULL) {
	return $form[$name] = new WebChemistry\Images\Addons\UploadControl($label, $namespace, $defaultValue);
});

Edit: Lomítko / by se nemělo používat, protože to je značení pro namespace (Asi jsem to zapomněl ošetřit, alespoň vím co opravit), doporučil bych spíše pomlčku než lomítko.

Editoval Antik (9. 2. 2015 14:42)

Pavel Janda
Člen | 977
+
0
-

Tedy ony ty „force vícenásobne“ namespaces nefungují ani ze začátku. Nevygeneruje se unikátní jméno uploadu. (Nevygeneruje se například lnsdzmdj0s_._).

Pavel Janda
Člen | 977
+
0
-

Lomítko / by se nemělo používat, protože to je značení pro namespace (Asi jsem to zapomněl ošetřit, alespoň vím co opravit), doporučil bych spíše pomlčku než lomítko.

Rozumím, že používáš / jako delimiter pro namespace, ale co když chci mít „namespaces“ více zanořené, tedy co když chci sázet obrázky do adresářové strukturyavatars/substr(uniqid(), -2, 2)? Tedy nějak takto:

<?php
images/
    avatars/
        ab/
        bc/
        df/
?>

Nebylo by lepší nahradit 87. -X. řádek PropertyAccess něčím takovým:

<?php
preg_match('/(.*)\/([^\/].*)/', $name, $matches);

$this->name = $matches ? end($matches) : $name;
$this->namespace = $matches ? $matches[1] : NULL;
?>

?

A potom ještě poupravit upload, aby se opravil i ten vstupní krok?

Martk
Člen | 661
+
0
-

Uvažoval jsem u zanořených složkách (Zatím jsem neměl důvod to používat), problém vytvořit to určitě není a nejspíše to udělám.

Pavel Janda
Člen | 977
+
+2
-

Antik napsal(a):

Uvažoval jsem u zanořených složkách (Zatím jsem neměl důvod to používat), problém vytvořit to určitě není a nejspíše to udělám.

Bylo by to perfektní. :)

Uvedu jeden z důvodů:
Pokud by web s velmi velkou návštěvností ukládal všechny obrázky do jednoho adresáře, tak přesto, že filesystemu by to bylo jedno, programátorovi by po zapnutí FileZilly nebo Commandru (Ne že bych používal Win) pokaždé spadl počítač. To je jednoduché – občas prostě na to FTP musím vlézt a něco vzít/smazat/upravit. Nechtěl bych otvírat adresář („složku“), kde jsou miliony obrázků. Pokud to rozdistribuujeme například pomocí posledních pár znaků uniqid(), tak se nám to hezky rozprostře.

Mimochodem, tvoje extension funguje na zanořené adresáře, pokud uvedu namespace ‚blabla/bubu‘, ale jsou tam zatím ty dva výše zmínění brouci. (Negenerování unikátního prefixu a to druhé, o čem jsem již mluvil a opravil 3 řádkami kódu).

Pavel Janda
Člen | 977
+
0
-

Mimochodem, tvoje extension funguje na zanořené adresáře, pokud uvedu namespace ‚blabla/bubu‘, ale jsou tam zatím ty dva výše zmínění brouci. (Negenerování unikátního prefixu a to druhé, o čem jsem již mluvil a opravil 3 řádkami kódu).

Moment, já to blbě pochopil. Ten unikátní prefix se generuje jen pokud již existuje daný soubor, že? Tím je to jasné, funguje to.

Martk
Člen | 661
+
0
-

Verze 1.2

  • Doplněny podsložky pro namespaces.
  • Zabezpečeno odstraňovaní pro podsložky namespaces (Budou odstraněny obrázky patřící pro danou namespace).
  • Exception pro obrázek obsahující / ve jméně.
  • Upravena registrace UploadControl.
  • Opravena chyba pro změnu obrázku s %.
  • Možnost zakázání generování změny velikosti obrázku (Hodící hlavně pro generování z Presenteru).
  • Dokumentace opravena.
  • Další drobné úpravy.

Plány pro další verzi (Budu rád pro každý návrh):

  • Multiupload pro UploadControl

Děkuji hlavně uživateli @Beton

Editoval Antik (9. 2. 2015 17:02)

Pavel Janda
Člen | 977
+
0
-

Funguje perfektně, opravdu chválím rozšíření, moc mi usnadní práci.

enumag
Člen | 2118
+
0
-

@Antik V prvním příspěvku zmiňuješ „Generace obrázku pomocí url.“ – to funguje jak? Očekával bych že to bude přes nějaký router, ale žádný jsem ve zdrojácích nenašel.

akadlec
Člen | 1326
+
0
-

no není tam, taky sem ho tam čekal a ušetřilo by mi to práci, a proto si dělám vlastní verzi ;)

enumag
Člen | 2118
+
+1
-

@akadlec :-D A ta je / bude někde na GH nebo ne?

Editoval enumag (10. 2. 2015 9:51)

akadlec
Člen | 1326
+
+1
-

@enumag zatím u mě na disku, na gh snad během týdne. Principielně vychází z uvedené ext. od barbijana a implementuje micropresenter, protože stejně jako u assetsloaderu chci aby obrazky byly na subdoméně kvůli paralelnímu načítání a zároveň bych rád přidal podporu pro jiná než lokální úložiště, např. S3 od amazonu.

iguana007
Člen | 970
+
0
-

@Antik

Antik napsal(a):
Plány pro další verzi (Budu rád pro každý návrh):

  • Multiupload pro UploadControl

TIP: Pokud by si se rozhodl pro implementaci některého z MultiUploaderu, který využívá v případě nedostupné podpory HTML5 flashovou instanci, tak je třeba myslet na to, že flashové uploady nefungují bez hacku pro aplikace, kde je nutnost přihlášeného uživatele, protože flash pak ruší session – více o workaroundu zde:
https://github.com/…leUpload.php#L92 + plno vláken tady na fóru, kde se to již nespočetně krát řešilo…

Martk
Člen | 661
+
0
-

@enumag Momentálně pomocí Traitu v budoucnu pomocí toho zmiňovaného routeru.

@iguana007 Flashy se chci vyhnout, ale ještě nad tím budu přemýšlet jak to udělám, mimochodem díky za poznámku.

flexroad
Člen | 117
+
0
-

Moc rad bych podekoval za skvele rozsireni!!!

Rad bych se zeptal, jestli a jak jde nektere velikosti obrazku generovat ihned po uploadu.

V handle jsem zkousel

$this->imageStorage->create($filename, "1200x");

ale bohuzel se nic nestane.
Jde mi o to, ze soubory uploaduju ajaxem a pote co se mi soubor upne na server, potrebuju neco jako:

<img class="img-responsive" src="'+ data.result.files.medium +'" srcset="'+data.result.files.big+' 1280w, '+data.result.files.medium+' 640w, '+data.result.files.small+' 320w" sizes="100%"/>

Soubor se sice ulozi jak ma do „original“, ale pokud vracim cestu na jeho thumbnail, tak z nejakeho duvodu neexistuje, dokud nerefreshnu celou stranku.

Diky za cokoliv!!!

Martk
Člen | 661
+
+1
-

@flexroad Nyní jsem vytvořil commit (Je potřeba stáhnout dev verzi), který umožňuje co potřebuješ.

UploadControl to zatím neumí, ale píšu si to na seznam.

Příklad:

// Pro upload
$upload = $imageStorage->fromUpload($values->upload, 'namespace');

// Pro string
$upload = $imageStorage->fromContent($string, 'namespace');

// Namespace
$upload->setNamespace('namespace');
// Velikosti
$upload->setSize('100x250');
$upload->setHeight(100);
$upload->setWidth(250);
// Nastavení kvality
$upload->setQuality(80);
// Nastavení flagu
$upload->setFlag('exact');

$upload->setCallback(function (Nette\Utils\Image $image) {
	// Zpracování

	return $image;
});

// Uložení
$upload->save();

Editoval Antik (10. 2. 2015 17:36)

akadlec
Člen | 1326
+
0
-

@Antik tak to bych si dovolil tvrdit že to pomocí traitu neděláš. Ty přece jen upravíš cestu k obrázku tak aby byla relativní a odkazovala tak na umístě v document rootu. Takže obrázek bude vždy na stejné doméně jako celé stránky. U tohoto řešení jsem to měl předěláno tak že jsem si mohl zadefinovat baseUrl takže jsem obrázky mohl poskytovat ze subdomény. A úplně nej je to mít onDemand, a generovat je až na základě požadavku, takže to ještě zrychlí procesování požadavku.

flexroad
Člen | 117
+
0
-

@Antik … kdyz pouziju soucasny dev-master, aplikace prestane fungovat.

V aplikaci pouzivam nejake controls, ktere registruju a pouzivam standardnim zpusobem. Po update na dev-master mi aplikace vrati chybu:

Component name must be integer or string, NULL given … pro nasledujici radek

$service = new App\Controls\UserPanelControl($this->container->getService('images.presenter'));

Extenzi mam privesenou takto

extensions:
    images: WebChemistry\Images\DI\Extension

Nejaky napad?

@flexroad

akadlec
Člen | 1326
+
0
-

Což je logické že ti to vyhodí pokud máš standartní konstruktor pro komponentu. Ten má pouze dva parametry – name a parent a ty mu předáš jeden. Takže si uprav komponentu.

flexroad
Člen | 117
+
0
-

@akadlec… poradis mi s tim trochu?

V base presenteru registruju komponentu:

/**
 * @var \App\Controls\IUserPanelControlFactory
 * @inject
 */
public $userPanelFactory;

protected function createComponentUserPanel()
{
    $control = $this->userPanelFactory->create();
    return $control;
}

Samotna komponenta pak vypada takto:

namespace App\Controls;

use Nette\Application\UI\Form;

class UserPanelControl extends \Nette\Application\UI\Control{

    public function render()
    {

        $this->template->setFile(__DIR__ . '/default.latte');
        $this->template->render();

    }

}

interface IUserPanelControlFactory
{
    /**
     * @return UserPanelControl
     */
    function create();
}

A v config.neon mam pak:

services:
    userPanel:
        implement: App\Controls\IUserPanelControlFactory

PS: Hlavne ale nechapu, proc komponenta funguje, dokud neupdatuju ma webchemistry/images:dev-master.
S webchemistry/images:1.2.1 jede uplne v pohode.

Diky,

@flexroad

Editoval flexroad (11. 2. 2015 11:11)

flexroad
Člen | 117
+
0
-

…staci toto pridat do komponenty?

public function __construct(IContainer $parent = NULL, $name = NULL){

}

Kdyz to pridam, tak funguje, ale nerad bych, aby to do budoucna na necem vyhorelo :)

@flexroad

akadlec
Člen | 1326
+
+1
-

tebou vypsaná chyba nekoresponduje s uvedeným kódem. Dle chyby to předává jako parametr konstruktoru té komponenty službu z té images ext, ale tvoje komponenta nemá konstruktor.

Nejjednodušší je si to do té komponenty injectovat a nebo pokud to chceš přes kontrusktor tak aby splňoval pak volání parent konstruktoru.

flexroad
Člen | 117
+
0
-

@Antik : Diky moc za upravu.

Je super, ze muzu nastavit velikost obrazku uz pri uploadu!!! O to mi ale v puvodnim dotazu neslo. Spise resim, ze kdyz obrazek uploadnu, tak se ulozi POUZE jeho original do slozky „original“.

Potreboval bych ale, aby se zaroven ulozili nahledy v ruznych velikostech (napriklad … 320×, 768×, 1200x)

Pokud tyto velikosti pouziju v makru:

<img class="img-responsive" src="{img $picture->name, '768x'}" srcset="{img $picture->name, '1200x'} 1280w, {img $picture->name, '768x'} 640w, {img $picture->name, '320x'} 320w" sizes="100%"/>

…a refreshnu stranku, pozadovane velikosti se vygeneruji presne jak potrebuji.

Pokud ale stranku nerefreshnu, pozadovane velikosti se nevygeneruji, cehoz bych prave potreboval dosahnout…

Muj handle muze vypadat nejak takto:

public function handleUpload()
{
    $file = $_FILES['files'];
    $filename = $file['name'][0];
    $type = $file['type'][0];
    $tmp_name = $file['tmp_name'][0];
    $size = (int) $file['size'][0];
    $error = empty($file) ? UPLOAD_ERR_NO_FILE : UPLOAD_ERR_OK;

    $picture = new Picture();
    $picture->name = $filename;

    $filename = $this->pictures->save($picture) . "-" . $filename;

    $fileUpload = new FileUpload([
        'name' => $filename,
        'type' => $type,
        'tmp_name' => $tmp_name,
        'size' => $size,
        'error' => $error,
    ]);

    $upload = $this->imageStorage->fromUpload($fileUpload);
    $info = $upload->save();
	}

pro priklad… ajaxem zavolam vyse uvedeny handle (ktery ulozi pozadovany soubor do slozky „original“) a hned pote v novem okne otevru:
http://server.com/…age.jpg/320x, tak vidim jen puvodni velikost obrazku.

Pokud ale pouziju vyse uvedene makro, obrazky se vygeneruji do pozadovanych velikosti a stejna adresa:
http://server.com/…age.jpg/320x vrati zmensenou velikost, coz je kyzeny vysledek.

Neda se nejak predgenerovat po nahrani k vyse uvedenemu obrazek s sirkou 320× (do slozky 320x), stejne jako se tomu deje pokud pouziju macro primo v prezenteru(handle)? Nebo nemuze volani uvedene routy primo generovat pozadovane velikosti???

Jeste jednou srdecne DIKY,

@flexroad

Martk
Člen | 661
+
+1
-

@flexroad Ano uloží se jenom originální velikost obrázku, zmenšená verze se udělá, až když je potřeba (Při prvním požadavku na obrázek). Pokud by jsi chtěl i přesto vytvořit zmenšené verze, tak nyní to lze udělat takhle:

$upload = $this->imageStorage->fromUpload($fileUpload);
$info = $upload->save();

$name = (string) $info; // Absolutní jméno

$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createLink(); // Generace zmenšeného obrázku a vrácení cesty

$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createInfoLink(); // Generace zmenšeného a vrácení objektů s WebChemistry\Images\Image\Info
Pavel Janda
Člen | 977
+
0
-

Zdravím,

už hodinu nemůžu přijít na to, proč se obrázky neuploadují při ajaxovém požadavku. Pokud do formuláře přidám:

<?php
	$form->getElementPrototype()->class = 'ajax';
?>

upload obrázků přestane fungovat, pokud to umažu, formulář chodí.

Nějaké tipy? Díky.

Editoval Beton (11. 2. 2015 16:13)

Pavel Janda
Člen | 977
+
0
-

Používám netteForms.js, nette.ajax.js.

premek_k
Člen | 172
+
0
-

@Beton Voláš $.nette.init() ?

Pavel Janda
Člen | 977
+
0
-

Ano..

Všechny ostatní formulářové prvky se do ajaxového requestu propíšou, jenom ten upload ne.

Martk
Člen | 661
+
0
-

@Beton Zkus dumpnout upload pole (Za použití normálního uploadu a ajaxového požadavku).

Pavel Janda
Člen | 977
+
0
-

AJAX OFF:
Nette\Utils\ArrayHash #bfc8
image_auto_uid_cut ⇒ „images/82/Screen-Shot-2015–02–09-at-13.17.19.png“ (48)

AJAX ON:
Nette\Utils\ArrayHash #5ab9
image_auto_uid_cut ⇒ NULL

Fakt měním jenom tu třídu formuláře. :D

Martk
Člen | 661
+
0
-

Myslel jsem změnit addUploadImage za addUpload, aby se ukázal dump z Nette\Http\FileUpload. Myslím si, že se totiž vrátí v error kódu 4, což znamená „No file was uploaded.“.

Pavel Janda
Člen | 977
+
0
-

Stejně se chová i nativní ->addUpload(). Takže se omlouvám, nesouvisí to s tímto rozšířením.

Nicméně, kdybyste někdo přišel, proč nefunguje při ajax requestu file upload, budu rád.

Pavel Janda
Člen | 977
+
0
-

Ach tak, ano, 4.

flexroad
Člen | 117
+
0
-

Antik napsal(a):

@flexroad Ano uloží se jenom originální velikost obrázku, zmenšená verze se udělá, až když je potřeba (Při prvním požadavku na obrázek). Pokud by jsi chtěl i přesto vytvořit zmenšené verze, tak nyní to lze udělat takhle:

$upload = $this->imageStorage->fromUpload($fileUpload);
$info = $upload->save();

$name = (string) $info; // Absolutní jméno

$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createLink(); // Generace zmenšeného obrázku a vrácení cesty

$this->imageStorage->create($name, '200x300', 'flag')
->setNoImage(NULL) // Nebude se generovat no image
->createInfoLink(); // Generace zmenšeného a vrácení objektů s WebChemistry\Images\Image\Info

@Antik: Skvela prace!!! Toto funguje presne podle mych predstav!

Pavel Janda
Člen | 977
+
0
-

Byla to chyba nette.ajax.js.

Odkazuji: nette.ajax.js#2.0.0

akadlec
Člen | 1326
+
0
-

@Beton jakou verzi Nette.ajax používáš? Nejnovější nebo starší? Stačí si dohledat něco o problematice posílání souborů v ajaxovém requestu a budeš doma. Starší verze nette.ajax to neuměla, muselo se to hackovat (je to i zde na foru) no a v nové verzi to pak @vojtech.dobes už opravil.

EDIT: ne nebyla to chyba nette.ajax.js, je to prostě vlastnost AJAXových požadavků.

Editoval akadlec (11. 2. 2015 17:09)

Pavel Janda
Člen | 977
+
0
-

@akadlec No chyba to byla, nefungovalo to. Ale chápu, nemůžu to dávat za vinu autorovi nette.ajax.js.

akadlec
Člen | 1326
+
0
-

no chyba to nebyla, byla to prostě vlastnost. Aby se ajaxovým requestem poslaly i soubory, musel se poslat úplně jinak. Otevři si firebug a podívej se jak vypadá request se starou a novou verzi a uvidíš.

Jan Mikeš
Člen | 771
+
0
-

Je nejaka jednoducha cesta k tomu, ziskat finalni URL napr v service, jina, nez tato?

$imageSrc = $this->application->getPresenter()->link(":ImageStorage:generate", [$namespaceAndFilename]);
Martk
Člen | 661
+
0
-

Píšu z hlavy a z mobilu:

<?php
// WebChemistry\Images\Storage
$imageStorage->create($namespace)->createLink(); // Jedna se o basePath
?>

Název funkce (create) je celkem matoucí, možná ještě přejmenuji.

Jan Mikeš
Člen | 771
+
0
-

@Antik to je pravda! a diky :)

Jan Mikeš
Člen | 771
+
0
-

Rad bych nahlasil chybu
Komponenty, ktere vyuzivaji defaultni konstruktor:

	public function __construct($parent = NULL, $name = NULL)
	{
		parent::__construct($parent, $name);
	}

Nebo zadny nemaji (dedi od UI\Control) tak prestaly fungovat:
Nette\InvalidArgumentException
Component name must be integer or string, NULL given.

Vygenerovana tovarnicka:

public function create()
      {
          $service = new App\Components\GA\Control($this->container->getService('images.presenter')); // Zde vyskakuje exception
          $service->injectComponentFactories($this->container);
          $service->injectTemplateFactory($this->container->getService('157_App_Services_TemplateFactory'));
          return $service;
      }

Pokud v komponente, kde zadny konstruktor nemam, pridam tento kod:

	public function __construct($parent = NULL, $name = NULL)
	{
		// Zde zamerne nevolam predka!!!
	}

Tak vse zda se normalne funguje.
Jedna se o verzi b38acf3d30bfae97d692532d91a0778df4b0ee5d nette 2.2.7, PHP 5.6.5

S verzi 1.2.1 je vse bez problemu!