Blog. Just Blog

Поворот изображения на основании данных EXIF

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Поворот изображения на основании данных EXIF
Поворот изображения на основании данных EXIF

Сложно представить современный интернет без загруженных фотографий. Это социальные сети, форумы, фотогалереи, фотохостинги и множество других ресурсов и сервисов. В зависимости от композиции, фотографии могут быть как вертикальными, так и горизонтальными. Но ни одна камера технически не в состоянии сделать вертикальный снимок, все без исключения снимки делаются горизонтальными, а ориентация (угол поворота) кадра записывается в секцию EXIF. Программы просмотра считывают эти данные и показывают изображение с нужным углом поворота. При обработке графических файлов средствами PHP, информация об ориентации игнорируется, поэтому при загрузке фотографий на различные сайты полученное изображение может оказаться повернутым, так как именно в таком виде снимок был сделан камерой. Конечно, можно предложить пользователю выполнить предварительную обработку фотографии в каком-нибудь графическом редакторе, но, во-первых, не все пользователи умеют с ними работать, а во-вторых, порой бывает очень сложно объяснить людям, почему у них в программе просмотра все отображается как надо, а после загрузки "все сломалось".

В качестве решения проблемы в различных интернетах предлагается считывать данные EXIF функцией exif_read_data, а затем на основании тега "Orientation" поворачивать изображение. Что-то вроде такого:
  1. $image imagecreatefromjpeg($file_path);
  2. // Прочитать данные EXIF
  3. $exif exif_read_data($file_path);
  4. if (!empty($exif['Orientation'])) {
  5.     switch ($exif['Orientation']) {
  6.         // Поворот на 180 градусов
  7.         case 3: {
  8.             $image imagerotate($image,180,0);
  9.             break;
  10.         }
  11.         // Поворот вправо на 90 градусов
  12.         case 6: {
  13.             $image imagerotate($image,-90,0);
  14.             break;
  15.         }
  16.         // Поворот влево на 90 градусов
  17.         case 8: {
  18.             $image imagerotate($image,90,0);
  19.             break;
  20.         }
  21.     }
  22. }
С виду решение кажется хорошим, но это лишь на первый взгляд. Справедливости ради скажу что да, действительно, в большинстве случаев это будет работать. Но PHP на сервере может быть собран без модуля поддержки EXIF, из-за этого функция exif_read_data будет недоступна со всеми вытекающими последствиями. На выделенном сервере или на хорошем виртуальном хостинге это еще можно решить, поставив задачу админу, но есть же и бесплатные хостинги, и дешевые тарифы с ограниченным функционалом, да мало ли что. А главная проблема заключается в том, что, как выяснилось, функция exif_read_data может работать некорректно даже в последней версии PHP. Например, если взять фотографию с фотоаппарата Nikon D800, затем уменьшить ее в программе XnView и сохранить изменения, то exif_read_data рухнет с ошибкой. При этом файл абсолютно корректный, любая программа для просмотра или редактирования открывает его без каких-либо ошибок, внутренняя структура точно соответствует спецификации формата JPEG.

В процессе разработки своих программ я неплохо изучил внутреннее устройство JPEG-файлов, поэтому было принято решение разбирать секцию EXIF самостоятельно без использования штатных функций PHP и искать там тег "Orientation". Вот что у меня получилось.
  1. $orientation=0;
  2. $f=fopen($file_path,'r');
  3. $tmp=fread($f2);
  4. if ($tmp==chr(0xFF).chr(0xD8)) {
  5.     $section_id_stop=array(0xFFD8,0xFFDB,0xFFC4,0xFFDD,0xFFC0,0xFFDA,0xFFD9);
  6.     while (!feof($f)) {
  7.         $tmp=unpack('n',fread($f,2));
  8.         $section_id=$tmp[1];
  9.         $tmp=unpack('n',fread($f,2));
  10.         $section_length=$tmp[1];
  11.  
  12.         // Началась секция данных, заканчиваем поиск
  13.         if (in_array($section_id$section_id_stop)) {
  14.             break;
  15.         }
  16.  
  17.         // Найдена EXIF-секция
  18.         if ($section_id==0xFFE1) {
  19.             $exif=fread($f,($section_length-2));
  20.             // Это действительно секция EXIF?
  21.             if (substr($exif,0,4)=='Exif') {
  22.                 // Определить порядок следования байт
  23.                 switch (substr($exif,6,2)) {
  24.                     case 'MM': {
  25.                         $is_motorola=true;
  26.                         break;
  27.                     }
  28.                     case 'II': {
  29.                         $is_motorola=false;
  30.                         break;
  31.                     }
  32.                 }
  33.                 // Количество тегов
  34.                 if ($is_motorola) {
  35.                     $tmp=unpack('N',substr($exif,10,4));
  36.                     $offset_tags=$tmp[1];
  37.                     $tmp=unpack('n',substr($exif,14,2));
  38.                     $num_of_tags=$tmp[1];
  39.                 }
  40.                 else {
  41.                     $tmp=unpack('V',substr($exif,10,4));
  42.                     $offset_tags=$tmp[1];
  43.                     $tmp=unpack('v',substr($exif,14,2));
  44.                     $num_of_tags=$tmp[1];
  45.                 }
  46.                 if ($num_of_tags==0) { return true; }
  47.  
  48.                 $offset=$offset_tags+8;
  49.  
  50.                 // Поискать тег Orientation
  51.                 for ($i=0$i<$num_of_tags$i++) {
  52.                     if ($is_motorola) {
  53.                         $tmp=unpack('n',substr($exif,$offset,2));
  54.                         $tag_id=$tmp[1];
  55.                         $tmp=unpack('n',substr($exif,$offset+8,2));
  56.                         $value=$tmp[1];
  57.                     }
  58.                     else {
  59.                         $tmp=unpack('v',substr($exif,$offset,2));
  60.                         $tag_id=$tmp[1];
  61.                         $tmp=unpack('v',substr($exif,$offset+8,2));
  62.                         $value=$tmp[1];
  63.                     }
  64.                     $offset+=12;
  65.  
  66.                     // Orientation
  67.                     if ($tag_id==0x0112) {
  68.                         $orientation=$value;
  69.                         break;
  70.                     }
  71.                 }
  72.             }
  73.         }
  74.         else {
  75.             // Пропустить секцию
  76.             fseek($f, ($section_length-2), SEEK_CUR);
  77.         }
  78.         // Тег Orientation найден
  79.         if ($orientation) { break; }
  80.     }
  81. }
  82. fclose($f);
  83.  
  84. $image imagecreatefromjpeg($file_path);
  85. if ($orientation) {
  86.     switch($orientation) {
  87.         // Поворот на 180 градусов
  88.         case 3: {
  89.             $image=imagerotate($image,180,0);
  90.             break;
  91.         }
  92.         // Поворот вправо на 90 градусов
  93.         case 6: {
  94.             $image=imagerotate($image,-90,0);
  95.             break;
  96.         }
  97.         // Поворот влево на 90 градусов
  98.         case 8: {
  99.             $image=imagerotate($image,90,0);
  100.             break;
  101.         }
  102.     }
  103. }
Такой скрипт гарантированно будет работать с любыми секциями EXIF в JPEG-файлах, даже если они повреждены или по какой-то другой причине не могут быть обработаны средствами штатных функций PHP. Графические файлы других форматов не поддерживаются.

Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 4903 | Комментариев: 11

Метки: PHP, графика, Exif
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Hovo (06.02.2017 в 15:27):
hovo731 skype, hovo73.am@mail.ru
Hovo (06.02.2017 в 15:17):
ManHunter, я согласен за деньги ! майл оставить?
ManHunter (06.02.2017 в 13:27):
Телепатией не владею, чтобы исправлять чьи-то ошибки без исходного кода и подробных логов. А чужие проекты я дорабатываю только за деньги.
Hovo (05.02.2017 в 19:21):
ManHunter, а что нужно добавить,чтобу не получать ошибку cannot proceed empty imagick file ? а просто добавить, как есть
Спасибо!
ManHunter (05.02.2017 в 16:36):
Не с чем там разбираться, EXIF в файле нет.
HOVO (05.02.2017 в 02:23):
ManHunter, Здравствуйте ! Спасибо вам за этот код, но у меня возникли проблемы с изображениями с JPEG Adobe Tags, которые не имеют IFD0, (0xFF, 0xE14),
вот ссылка на нарабочее изображение http://ivi.am/img/headers.jpg .
Помогите пожалуйста разобратся.
Вячеслав (03.08.2016 в 16:29):
Скрипт - работает. Автору большой респект и уважуха.
Геннадий (06.01.2016 в 12:59):
Спасибо за решение.

Спасибо за решение. Практически применил для решения одной задачи с поворотом изображения.
Дмитрий (31.10.2015 в 16:58):
Весьма познавательно, положу в копилку.

Озадачился поворотом изображений и сейчас меня терзает один вопрос (ответа ни где не нашел) - как повернуть изображение (только средствами PHP, GD), без потери качества? Ибо заметил что любой поворот, заметно ухудшает качество картинки. Может поможете, судя по вашим статьям, у вас опыт в разработке PHP скриптов, несрамнимо больше моего.
ManHunter (08.05.2015 в 17:30):
Фотографировать ты можешь с любым поворотом, изображение все равно будет сохранено ландшафтно. Обрежь от вертикальной фотографии exif и открой в какой-нибудь фотожопе, все непонятки отпадут.
master-ufa (08.05.2015 в 17:24):
"Но ни одна камера технически не в состоянии сделать вертикальный снимок, все без исключения снимки делаются горизонтальными,"
не понятно.
Если надо сфотографировать человека в полный рост, то поворачиваю фотоаппарат на 90 градусов. Это не правильно?

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2017
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.09 сек. / MySQL: 2 (0.004 сек.) / Память: 4.5 Mb
Наверх