Сжатие картинок на сервере

Оказывается картинки на ваших сайтах весят больше, чем положено. Происходит это по причине не оптимальных алгоритмов сжатия с помощью различных программ. Кстати, фотошоп File->Save for web довольно хорошо оптимизирует изображение. А вот ACDSee и уж тем более php-библиотеки типа GD дают далеко не оптимальный результат.

Кроме того, в изображении содержится дополнительная текстовая информация: дата снимка, параметры съемки, модель фотоаппарата и т.д. Пускай это 1%, но зачем это загружать пользователю.

Все ниже перечисленное относится к сжатию без потери качества. Т.е. картинка визуально те теряет качества.

Примеры

Одна из этих картинок весит 1.7 кб, другая 2.3 кб

Одна из этих картинок весит 32 кб, другая 42 кб

Для чего это нужно?

Для того, чтобы ускорить загрузку страницы, особенно для мобильных устройств (пост о мобильниках будет позже). Ну и место на диске не у всех резиновое.

Например, для этого поста, где половина фоток была оптимизирована Фотошопом, сжатие дало 5%, вроде бы не так много, но в абсолютном значении это 250кб, что уже хорошо.

И еще я заметил, старые фотки от первых цифровых камер или старые сканы сжимаются на 50%. Хотя внешне эти фотки выглядят и так сжатыми до предела.

С чего начать?

Сжатие производится с помощью библиотек, которые нужно установить на сервер

для jpg: Jpegoptim
установка apt-get install jpegoptim

для png: OptiPNG
установка apt-get install optipng

для gif: gifsicle
установка apt-get install gifsicle

Как сжимать

Из консоли запускаем

jpegoptim /path_to_img/foto.jpg --strip-all
optipng /path_to_img/foto.png
gifsicle --batch -O2 /path_to_img/foto.gif

Есть команды для сжатия всех картинок в директории, но всё равно ручками замучаешься. Поэтому процесс нужно автоматизировать. Вообще, вся затея ниже имеет смысл, если у вас десятки сайтов и десятки тысяч изображения.

Итак, нам нужно
1) оптимизировать уже существующие изображения
2) оптимизировать новые изображения, добавляемые редакторами на сайт

При этом нужно запоминать, что мы уже оптимизировали, чтобы не делать двойную работу. Кстати, если изображение уже было оптимизировано, но при повторной оптимизации с изображением ничего не происходит.

Учет картинок

Удобнее всего хранить пути к изображениям в базе и периодически (по крону) оптимизировать их.

Создадим отдельную базу img с единственной таблицей img

CREATE TABLE `img` (
  `url` varchar(200) NOT NULL,
  `is_compress` tinyint(1) NOT NULL DEFAULT '0',
  UNIQUE KEY `url` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Смысл в том, чтобы у каждой картинки был уникальный адрес в таблице (индекс UNIQUE KEY). Т.е. если у вас на сервере сайты c одинаковыми картинками, то в таблице они так бы и лежали
/ekimoff.ru/img/logo.jpg
/tinkoff.ru/img/logo.jpg

Флаг is_compress — показывает статус обработки
0 — еще не оптимизировали
1 — успешно оптимизировали
2 — ошибка при оптимизации

Как собрать адреса всех изображений на сервере

<?php
// константы лучше вынести в конфиг
define('ROOT','/var/www/nick/data/www'); // путь до хостов
define('DB_IMG_USER','img');
define('DB_IMG_PASSWORD','password');
define('DB_IMG_HOST','localhost');
define('DB_IMG_DB','img');

mysql_connect(DB_IMG_HOST, DB_IMG_USER, DB_IMG_PASSWORD);
mysql_select_db(DB_IMG_DB);

function recursive($dir) {
	$odir = opendir($dir);
	while (($file = readdir($odir)) !== FALSE) {
      if ($file == '.' || $file == '..') {
         continue;
      }
      else {
      	 if (is_file($dir.DIRECTORY_SEPARATOR.$file) &&
      	 	 (stripos($file,'.jpg') ||
      	 	  stripos($file,'.jpeg') ||
      	 	  stripos($file,'.png') ||
      	 	  stripos($file,'.gif'))
      	 	 ) {
      	 	 $url = str_replace(ROOT,'',$dir.DIRECTORY_SEPARATOR.$file);
		  mysql_query('INSERT IGNORE INTO img(url) VALUES("'.$url.'")');
      	 	 echo $url.'<br>';
      	 }
      }

      if (is_dir($dir.DIRECTORY_SEPARATOR.$file)) {
         recursive($dir.DIRECTORY_SEPARATOR.$file);
      }
	}
 	closedir($odir);
}
recursive(ROOT.'/ekimoff.ru/download');
recursive(ROOT.'/ekimoff.ru/img');
recursive(ROOT.'/site.ru/images');
?>

Собственно, это рекурсивная функция, которая обходит все поддиректории и записывает пути в таблицу img. Т.е. вам нужно предварительно собрать список всех основных директорий с картинками. Если картинок много, то собрать картинки одним махом в виде recursive(сервер) может привести к зависанию, т.к. рекурсия трудоемкая процедура для сервера.

Как собрать адреса добавляемых изображений

На каждом сайте есть 1-2 точки загрузки изображений (типа /ajax/upload.php)
В эти скрипты нужно вставить

// константы лучше вынести в конфиг
define('DB_IMG_USER','img');
define('DB_IMG_PASSWORD','password');
define('DB_IMG_HOST','localhost');
define('DB_IMG_DB','img');
define('HOST','ekimoff.ru');

$img_db_link = mysql_connect(DB_IMG_HOST, DB_IMG_USER, DB_IMG_PASSWORD);
mysql_select_db(DB_IMG_DB, $img_db_link);
mysql_query('INSERT IGNORE INTO img(url) VALUES("/'.addslashes(HOST.$sFileLocalPath).'")', $img_db_link);

$img_db_link — ссылка на соединение с базой картинок (чтобы не было конфликта, если у вас уже есть подключение к текущей базе сайта)
$sFileLocalPath — относительный путь к картинке, например /img/foto/1.jpg — обычно этот путь вы генерируете при загрузке картинки.
HOST — это домен на который заливается изображение (для получения полного пути в таблице /ekimoff.ru/img/foto/1.jpg). Вообще эту константу можно еще где-либо использовать по коду.

Как вариант, оптимизировать изображение на лету, т.е. сразу в скрипте прописывать
shell_exec("jpegoptim ".escapeshellarg($_SERVER['DOCUMENT_ROOT'].$sFileLocalPath)." --strip-all");
Но хранение в единой базе как-то добавляет больше порядка.

Запускаем Берлагу оптимизацию

<?php
// константы в конфиг
define('ROOT','/var/www/nick/data/www'); // путь до хостов
define('DB_IMG_USER','img');
define('DB_IMG_PASSWORD','password');
define('DB_IMG_HOST','localhost');
define('DB_IMG_DB','img');

mysql_connect(DB_IMG_HOST, DB_IMG_USER, DB_IMG_PASSWORD);
mysql_select_db(DB_IMG_DB);

// берем 20 картинок, которые еще не оптимизированы
$q = mysql_query('SELECT * FROM img WHERE is_compress=0 LIMIT 20');
while ($res = mysql_fetch_assoc($q)) {
	$img = $res['url'];
	if (stripos($img, '.png')) {
		$result = shell_exec("optipng ".escapeshellarg(ROOT.$img));
		echo $result.'<br>';
		if (stripos($result,'%')!==false) {
             mysql_query('UPDATE img SET is_compress=1 WHERE url="'.$img.'"');
		}
		else {
            mysql_query('UPDATE img SET is_compress=2 WHERE url="'.$img.'"');
		}
	}
	elseif (stripos($img, '.jpg') || stripos($img, '.jpeg')) {
		$result = shell_exec("jpegoptim ".escapeshellarg(ROOT.$img)." --strip-all");
		echo $result.'<br>';
		if (stripos($result,'%')!==false) {
             mysql_query('UPDATE img SET is_compress=1 WHERE url="'.$img.'"');
		}
		else {
             mysql_query('UPDATE img SET is_compress=2 WHERE url="'.$img.'"');
		}
	}
	elseif (stripos($img, '.gif')) {
                // gifsicle не выдает результата сжатия
		shell_exec("gifsicle --batch -O2 ".escapeshellarg(ROOT.$img));
		mysql_query('UPDATE img SET is_compress=1 WHERE url="'.$img.'"');
	}
}
?>

Берем каждую картинку и с помощью shell_exec() запускаем нужный процесс (jpegoptim, optipng, gifsicle) в зависимости от расширения файла.

Не нужно брать сразу всю таблицу — сервер повиснет. Брем по 5-100 изображений за раз. Вешаем этот скрипт в cron на каждую минуту. Т.е. при добавлении новой картинки на сайт — она будет оптимизирована в течение минуты.

$result — это текстовый результат сжатия. В нем содержится насколько процентов ужался файл — помечаем флагом is_compress=1. Если такой информации нет, то произошла ошибка и помечаем этот файл флагом is_compress=2

Вот так это выглядит при выводе на экран (echo $result.'<br>';)

Как правило, ошибки происходят из-за неверного расширения файла. Например, была гифка, а на сайте у нее расширение jpg — при этом браузеры могут нормально ее отображать. Можно было конечно по mime-type определять расширение.

Права на файлы

Т.к. shell_exec выполняется из php-скрипта, то и владелец у изображений должен быть как у Апача, т.е. www-data.

Часто бывает так, что сайт был залит через ftp под root, поэтому из php оптимизировать и перезаписать такие файлы не получится.

Поэтому нужно убедиться, что нужные директории и файлы принадлежат www-data и выставлены права. У директорий, в которые заливаются файлы, права 755, а у изображений 644.

Сделать это можно через консоль, ftp-менеджер или ISP-менеджер (удобная штука для администрирования сервером)

#1

Привет. Есть вариант вообще на серваке картинки не хранить - использовать фликр и им подобные.
Из плюсов для себя отметил:
+ Быстрая загрузка
+ Смена размеров изображений(подробнее https://www.flickr.com/services/api/misc.urls.html)
+ Не занимают место на диске
+ Есть апи
+ Возможность создавать и выгружать альбомы.
Из минусов:
- У некоторых офисных работников могут не отображаться картинки(админы закрыли доступ), процент очень мал.
- Для избежания потери фото желательно делать бекап(можно на яндекс диск или др. облако).
- Нет автокомпресси(правда появился новый сервис от гугла->Google Photos, вроде сжимает http://petapixel.com/2015/05/30/jpeg-compression-test-google-photos-vs-jpegmini/

Putin, 30.06.2015 - 18:00
#2

По мне так самый большой минус Фликра - забанят и будешь потом маяться с восстановлением (те же урлы менять). Бан можно получить внезапно из-за какой-нибудь мелочи по авторскому праву. Вон как легко приложения выкидывают из апстора, гугл.маркета и т.д. В Фэусбуке ты получаешь бан аккаунта на неделю, если загрузить полуголого ребенка (типа педофилия). В Ютубе несколько жалоб от правообладателей и тоже бан.

К тому же, как правило, у вебмастера уже есть десятки старых сайтов, где все настроено по старинке и переделывать всё на Фликр еще тот геморрой.

По этой же причине выбрал блог на своем домене, а не ЖЖ, хотя плюсы у ЖЖ есть конечно по сравнению с Вордпресом.

admin, 30.06.2015 - 18:06
Оставить комментарий