Tag Archives: grafika

webmastering

Wygrzebane z GitHuba (2) : Imagine

Dzisiaj drugi odci­nek Waszego ulubio­nego serialu: Wygrze­bane z GitHuba. Wszyst­kie stacje TV rywa­li­zują o wyłącz­ność na emisję kolej­nego odcinka, padają kwoty sied­mio­cy­frowe wyra­żane w zimba­bwiań­skich dolarach…

Imagine to udana próba ujed­no­li­ce­nia inter­fej­sów różnych biblio­tek graficz­nych w zakre­sie mani­pu­la­cji obra­zami w PHP. Nieważne czy pod maską mamy GD, Image­Ma­gick czy Gmagick. Wszyst­kie opera­cje wyko­namy za pomocą jedno­li­tego inter­fejsu. Doku­men­ta­cja do znale­zie­nia tutaj.

Pomimo tego iż projekt ma dopiero nieco ponad rok, jest przez ten czas wyko­nany kawał dobrej roboty. Projekt ma ładną struk­turę wyra­żoną w name­spa­cach, przez co można śmiało łado­wać tylko potrzebne klasy za pomocą stan­dar­do­wego SplC­las­sLo­adera (do pocią­gnię­cia np. stąd).

O Imagine pisał php|architect, jest również prezen­ta­cja.

Poni­żej zade­mon­struję jak można użyć tego cuda do zmniej­sze­nia zestawu obraz­ków do jedno­li­tych kwadra­tów i wyświe­tlić na ekran w locie. To oczy­wi­ście metoda dla maso­chi­stów CPU, bo normal­nie czło­wiek prze­cież zapi­suje obrazki najpierw na dysk, a dopiero potem wyświe­tla. Efekt, który chcemy uzyskać będzie wyglą­dał tak:

<?php
require_once 'SplClassLoader.php';
 
// rejestrujemy przestrzen nazw Imagine
// i wskazujemy gdzie jej szukac
$classLoader = new SplClassLoader('Imagine', 'lib');
$classLoader->register();
 
// importujemy niezbedne klasy
use Imagine\ImageInterface;
use Imagine\Gd\Imagine;
use Imagine\Image\Box;
use Imagine\Image\Color;
use Imagine\Image\Point;
 
$columns = 2;
$images = 4;
$singleImgSize = 250;
 
// licze calkowita szerokosc tla
$backgroundXSize = (int) $columns * $singleImgSize;
// wysokosc zaokraglam w gore dla nieparzystych
$backgroundYSize = (int) $singleImgSize * ceil($images / $columns);
 
// jezeli mam i chce skorzystac z ImageMakicka
// to po prostu importuje wyzej
// use Imagine\Imagick\Imagine;
$imagine = new Imagine();
 
// tworze pusty obrazek w czarnym kolorze i 0 przezroczystosci
$background = $imagine->create(new Box($backgroundXSize, $backgroundYSize), new Color('000', 0));
 
// tablica z nazwami obrazkow do przeskalowania
// i wyswietlenia
$imagesArray = array('img1.jpg', 'img2.jpg', 'img3.jpg', 'img4.jpg');
 
// punkty startowe wklejania
$x = 0;
$y = 0;
 
foreach ($imagesArray as $img) {
  // otwieram obrazek z dysku,
  // zmniejszam go do kwadratu
  // uzywam trybu OUTBOUND,
  // czyli pilnuję proporcji i wywalam fragmenty dluzszego boku
  $image = $imagine->open('../images/' . $img)
            ->thumbnail(new Box($singleImgSize, $singleImgSize), ImageInterface::THUMBNAIL_OUTBOUND);
 
  // wklejam zmniejszony obrazek do tla
  // podajac lokalizacje lewego gornego rogu wklejania
  $background->paste($image, new Point($x, $y));
 
  // licze pozycje startowa dla nastepnego obrazka  
  $x += $singleImgSize;
  if ($x >= $canvasXSize) {
    $x = 0;
    $y += $singleImgSize;
  }
}
 
// obiekt Image obsluguje metode
// __toString() w postaci danych binarnych PNG
// wystarczy tylko podac naglowki
header('Content-Type: image/png');
// zamiast wyswietlac mozemy uzyc
// $background->save('obrazek.png',array('quality' => 85));
echo $background;

Oprócz typo­wych zadań typu skalo­wa­nie czy konwer­sja na inne formaty, projekt ma aspi­ra­cje na dosyć zaawan­so­wane mani­pu­la­cje obra­zami typu odwra­ca­nie w pozio­mie i pionie, refleksy, wykresy, ujed­no­li­cony inter­fejs ryso­wa­nia obiek­tów, histo­gramy itd.

Obrazki na licen­cji CC-BY poży­czone z Flic­kra. Właści­ciele praw do obraz­ków: pierw­szego, drugiego, trze­ciego, czwar­tego.

webmastering

Bezstratna optymalizacja plików JPG

Czyta­jąc mądrą książkę doty­czącą opty­ma­li­za­cji witryn inter­ne­to­wych natra­fi­łem na narzę­dzie jpeg­tran, które wykona dla nas kawał dobrej roboty bezstrat­nie zmniej­sza­jąc wiel­kość plików jpg.1 Dodat­kowo przy okazji możemy wyrzu­cić część lub całość meta­da­nych z pliku.

Użyt­kow­nicy czasem sami sobie robią krzywdę wrzu­ca­jąc zdję­cia do inter­netu robione komór­kami z GPSem. Raz, że komórki podają swój model i inne para­me­try (np. przy­słony), a dwa, że zapi­sują w meta­da­nych dokładną loka­li­za­cję wyko­na­nego zdję­cia (a tym samym np. czyje­goś domu).

Zmniej­sze­nie wiel­ko­ści pliku za pomocą jpeg­tran polega na opty­ma­li­za­cji tablic Hauf­f­mana i ewen­tu­al­nie doda­nie trybu progre­syw­nego (w miarę łado­wa­nia się zdję­cia polep­sza się jego jakość na stro­nie WWW). Co ciekawe, doda­nie progre­syw­no­ści również zaosz­czę­dza kilka KB w zdjęciu.

Walka toczy się o ok. 20% wiel­ko­ści pliku, czyli dosyć sporo. Jpeg­tran można sobie w Ubuntu znaleźć w Synap­ticu w ramach pakietu libjpeg-progs. Co do Windowsa radź­cie sobie sami ;-)

Na koniec przed­sta­wię owoc mojej godzin­nej pracy: klasę, za pomocą której w php możemy stero­wać poczy­na­niami jpeg­tran. Jak zwykle po Angiel­sku, ale za to komen­ta­rze i errory po Polsku żeby nie było że jakiś stron­ni­czy jestem. Jak to cudo działa? Na etapie tworze­nia obiektu przyj­muje 2 parametry:

  1. Co zrobić z meta­da­nymi pliku
    • none — wyrzu­cić wszyst­kie metadane
    • comments — zosta­wić tylko komen­ta­rze (mogą zawie­rać dane na temat licen­cji i/lub właściciela)
    • all — zosta­wić meta­dane tak jak są
  2. Jakie wyko­nać opera­cje na pliku
    • opti­mise — prze­pro­wa­dza opty­ma­li­za­cję tablic Hauffmana
    • progres­sive — dodaje tryb progresywny

Wystar­czy sobie utwo­rzyć obiekt JpegOp­ti­mi­ser i można za pomocą metody proces­sI­mage prze­twa­rzać obrazki, np. tak:

$jpg = new JpgOptimiser('none', array('optimise','progressive'));
$jpg->processImage('test.jpg', 'test_wyjsciowy.jpg');

Poni­żej kod klasy:

<?php
class JpgOptimiser {
 
    /**
     * @var array tablica dopuszczalnych parametrow operacji graficznych
     */
    protected static $VALID_PROCS = array('optimise', 'progressive');
 
    /**
     * @var array tablica dopuszczalnych parametrow operacji na metadanych
     */
    protected static $VALID_META = array('none','all','comments');
 
    /**
     * @var array tablica najczestszych bledow z wyjscia i komunikat bledu
     */
    protected static $REGEX_OUTPUT_ERRORS = array(
            array('/jpegtran: not found/', 'nie wykryto jpegtran'),
            array('/can\'t open/', 'nie mozna otworzyc pliku'),
            array('/not a jpeg/i', 'to nie jest plik jpg')
    );
 
    /**
     * @var string przygotowana lista argumentow do uruchomienia w konsoli
     */
    protected $argsChain;
 
    /**
     * Wybor listy operacji do wykonania.
     * @param string $meta operacje wykonywane na metadanych; dopuszczalne: none, comments, all
     * @param array $procs operacje graficzne; dopuszczalne: optimise, progressive
     */
    public function __construct($meta = 'none', array $procs = array('optimise', 'progressive')) {
        $this->argsChain  = $this->parseArgs($procs, self::$VALID_PROCS, ' -');
        $this->argsChain .= $this->parseArgs(array($meta), self::$VALID_META, ' -copy ');
    }
 
    /**
     * @param array $args tablica parametrow wejsciowych
     * @param array $valid tablica parametrow dopuszczalnych
     * @param string $prefix znak poprzedzajacy argument
     * @return string przetworzony ciag argumentow
     */
    private function parseArgs(array $args, array $valid, $prefix) {
        $output = '';
        foreach ($args as $arg) {
            if (in_array($arg, $valid)) {
                // dorzucam myslnik przed parametrem i spacje na koncu
                $output .= $prefix . $arg . ' ';
            }
        }
        return $output;
    }
 
    /**
     * Przetwarzanie pliku.
     * Docelowo lepszym rozwiazaniem bedzie uzywanie SPLFileInfo.
     * @param string $srcFile nazwa pliku wejscowego
     * @param string $dstFile nazwa pliku wyjscowego (moze byc taka sama jak wejscowy)
     * @return bool
     */
    public function processImage($srcFile, $dstFile) {
        $output = shell_exec('jpegtran' . $this->argsChain . '-outfile ' . escapeshellarg($dstFile) . ' ' . escapeshellarg($srcFile) . ' 2>&1');
        try {
            return $this->isProcessingOK($output);
        }
        catch(Exception $e) {
            echo $e->getMessage();
            return false;
        }
    }
 
    /**
     * @param mixed $output
     * @return bool
     * @throws Exception
     */
    private function isProcessingOK($output) {
        if ($output === null) {
            return true;
        }
        else {
            // szukam znanych komunikatow bledow
            foreach (self::$REGEX_OUTPUT_ERRORS as $error) {
                if (preg_match($error[0], $output) != 0) {
                    throw new Exception($error[1]);
                }
            }
            // nie znaleziono znanych, rzucam cala tresc wyjscia
            throw new Exception($output);
        }
    }
}
  1. S. Souders : Jesz­cze wydaj­niej­sze witryny inter­ne­towe. Przy­spie­sza­nie dzia­ła­nia serwi­sów WWW. Gliwice : Helion, 2010, s. 156–157.