Czytając mądrą książkę dotyczącą optymalizacji witryn internetowych natrafiłem na narzędzie jpegtran, które wykona dla nas kawał dobrej roboty bezstratnie zmniejszając wielkość plików jpg.1 Dodatkowo przy okazji możemy wyrzucić część lub całość metadanych z pliku.
Użytkownicy czasem sami sobie robią krzywdę wrzucając zdjęcia do internetu robione komórkami z GPSem. Raz, że komórki podają swój model i inne parametry (np. przysłony), a dwa, że zapisują w metadanych dokładną lokalizację wykonanego zdjęcia (a tym samym np. czyjegoś domu).
Zmniejszenie wielkości pliku za pomocą jpegtran polega na optymalizacji tablic Hauffmana i ewentualnie dodanie trybu progresywnego (w miarę ładowania się zdjęcia polepsza się jego jakość na stronie WWW). Co ciekawe, dodanie progresywności również zaoszczędza kilka KB w zdjęciu.
Walka toczy się o ok. 20% wielkości pliku, czyli dosyć sporo. Jpegtran można sobie w Ubuntu znaleźć w Synapticu w ramach pakietu libjpeg-progs. Co do Windowsa radźcie sobie sami
Na koniec przedstawię owoc mojej godzinnej pracy: klasę, za pomocą której w php możemy sterować poczynaniami jpegtran. Jak zwykle po angielsku, ale za to komentarze i errory po polsku żeby nie było że jakiś stronniczy jestem. Jak to cudo działa? Na etapie tworzenia obiektu przyjmuje 2 parametry:
- Co zrobić z metadanymi pliku
- none — wyrzucić wszystkie metadane
- comments — zostawić tylko komentarze (mogą zawierać dane na temat licencji i/lub właściciela)
- all — zostawić metadane tak jak są
- Jakie wykonać operacje na pliku
- optimise — przeprowadza optymalizację tablic Hauffmana
- progressive — dodaje tryb progresywny
Wystarczy sobie utworzyć obiekt JpegOptimiser i można za pomocą metody processImage przetwarzać 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);
}
}
}
- S. Souders : Jeszcze wydajniejsze witryny internetowe. Przyspieszanie działania serwisów WWW. Gliwice : Helion, 2010, s. 156–157. ↩