Я думаю каждый веб программист сталкивался с задачей ресайза картинок. Впрочем это касается не только ресайза, а любой обработки изображений в своих проектах. И думаю каждый уже имеет свой небольшой велосипед, класс, функцию которая ресайзит картинки. Но задумывались ли вы о том, что ваш велосипед наверняка не умеет работать с прозрачными картинками?
Честь и хвала тем программистам, которые могут удержать все в голове, и при написании подобного функционала они не забыли про прозрачность. А я вот забыл в одном из своих проектов… Хотя, чесно говоря, я взял нагугленный пример, чутка переделал для себя и встроил в проект. Но кто же знал, что клиент любит загружать прозрачные картинки…
В большинстве случаев отресайзенные картинки получали черный фон вместо прозрачного (как на картинке в заголовке статьи). Новый фон конечно может получится любого цвета, это все же зависит от самой картинки, и от ее формата. Но данная проблема известна все же как «черный фон при ресайзе прозрачных картинок».
Как всем известно, в вебе обычно используется три формата изображений: PNG, GIF, JPEG. Соответственно прозрачными могут быть только PNG и GIF. А JPEG прозрачность не поддерживает. То есть наша задача состоит в том чтобы разобраться как правильно ресайзить PNG и GIF. У этих форматов прозрачность реализованна по-разному, и обрабатывать их надо соответственно по-разному.
Ресайзим PNG
Я думаю ни для кого не секрет, что в PNG реализованна поддержка альфа канала. То есть цвет задается четырмя компонентами: R (красный), G (зеленый), B (синий), альфа. Альфа канал задает прозрачность конкретного пикселя. Впрочем это все вы должны уже знать, перейдем к сути.
Причин появления черного фона при ресайзе PNG три:
- Программист создает не полноцветное изображение. Т.е. использует функцию imagecreate вместо imagecreatetruecolor
- Программист забывает отключить наложение прозрачного цвета (или если правильно — «режим сопряжения цветов») с помощью функции imagealphablending. Дело в том что imagecreatetruecolor создает изображение с черным фоном. При включенном «режиме сопряжения цветов» прозрачный пиксель, не заменит фон, а будет рассчитан новый цвет в соответствии с новым цветом и цветом фона с учетом альфа канала обоих цветов — в итоге для полностью прозрачного пикселя получаем фоновой цвет — т.е. черный.
- Программист не включает сохранение альфа канала в выходной файл. За это отвечает функция imagesavealpha
Чтож, с причинами разобрались. Теперь реализация правильного ресайза PNG картинки:
//Создаем полноцветное изображение
$destination_resource=imagecreatetruecolor($newwidth, $newheight);
//Отключаем режим сопряжения цветов
imagealphablending($destination_resource, false);
//Включаем сохранение альфа канала
imagesavealpha($destination_resource, true);
//Ресайз
imagecopyresampled($destination_resource, $source_resource, 0, 0, 0, 0, $newwidth, $newheight, $oldwidth, $oldheight);
//Сохранение
imagepng($destination_resource, $destination_path);
Ресайзим GIF
А вот GIF-е прозрачность реализованна по-другому. В нем один из используемых цветов (любой, на выбор автора картинки) объявляется как прозрачный, и при отрисовке данной картинки пиксели имеющие данный цвет заменяются прозрачными пикселями. Такая вот «псевдо-прозрачность»:
И единственная причина по которой может появится фон в прозрачной GIF — программист забывает указать «прозрачный» цвет.
В php за это отвечает функция imagecolortransparent — задает прозрачный цвет. Но сначала необходимо узнать использует ли вообще наша GIF прозрачность, это делается с помощью этой же функции. Полный алгоритм должен выглядеть приблизительно так:
//Создаем изображение, кстати для GIF можно использовать обычную imagecreate, но лучше все таки везде использовать imagecreatetruecolor
$destination_resource=imagecreatetruecolor($newwidth, $newheight);
//Получаем прозрачный цвет
$transparent_source_index=imagecolortransparent($source_resource);
//Проверяем наличие прозрачности
if($transparent_source_index!==-1){
$transparent_color=imagecolorsforindex($source_resource, $transparent_source_index);
//Добавляем цвет в палитру нового изображения, и устанавливаем его как прозрачный
$transparent_destination_index=imagecolorallocate($destination_resource, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
imagecolortransparent($destination_resource, $transparent_destination_index);
//На всякий случай заливаем фон этим цветом
imagefill($destination_resource, 0, 0, $transparent_destination_index);
}
//Ресайз
imagecopyresampled($destination_resource, $source_resource, 0, 0, 0, 0, $newwidth, $newheight, $oldwidth, $oldheight);
//Сохранение
imagegif($destination_resource, $destination_path);
Можно конечно, чуть короче и проще, но на мой взгляд так намного нагляднее.
Вывод
А мораль сей басни такова — делайте бекапы) Причем здесь бекапы? А при том… Из-за того что я не учел прозрачность при ресайзе, я запарол несколько тысяч прозрачных картинок у клиента. И слава богу что я таки сделал полный бекап еще до внесения изменений, из которого впоследствии востановил картинки обратно.
Именно по этому стоит использовать фреймворки и либы. За пару команд можно сделать всё тоже самое даже не задумываясь, что там внутри.
Пожалуй да, это наилучший выход…
Хотя, при этом есть 1 минус. Когда нужно сделать что-то ну совсем нестандартное — приходится лезть в максимально низкий, для языка, уровень, и делать это там. Например, я одно время, вообще не писал sql, мне хватало рельсового activerecord. А потом понадобилось оптимизировать скорость пары запросов, и вот тут началось.. За-то, теперь почти специалист в этом)
Спасибо автору давно искал ответ на данный вопрос какже загрузить изображение с прозрачностью не заливая фон белым или инным цветом. Спасибо еще раз очень помогла мне ваша статья.
И кстате добавлю пожалуй ваш блог в закладки думаю не раз еще понадобится может чего и полезного тут найду :)
Спасибо, очень приятно слышать =)
Заметил ошибку у вас в коде про то как быть с псевдо-прозрачностью в gif:
Где «На всякий случай заливаем фон этим цветом» переменная цвета $transparent_destination не существует
нужно:
$transparent_destination_index
спасибо, исправил =)
я так понимаю, скрипт ресайза гифки не распространяется на анимацию?
Боюсь что нет. Мне не приходилось сталкиваться с ресайзом анимированных гифок.
Ааааа! пасибо огромное!
Все просто и понятно! Люблю когда вот так, прям как для чайников))))
И вам спасибо за позитивный комментарий)
Не работает. Уменьшает но фон черный!
Боюсь что где то у Вас все же ошибка.
У меня тоже черный фон. Посмотрите, пожалуйста, где ошибка… Заранее, спасибо.
Это явно не для чайников — «будет рассчитан новый цвет в соответствии с новым цветом и цветом фона с учетом альфа канала обоих цветов»
Честно сказать не знаю как это можно переформулировать, даже после того как вы сакцентировали на этом внимание… но вроде понятно же, нет?
Спасибо за разъяснение, но почему-то никак не могу добиться прозрачности водяного знака при наложении его на картинку. Вот такой код:
Причем на денвере все нормально работает, а на хостинге не хочет. Подскажите, может ли что-то зависеть от настроек сервера? Может версия пхп или gd? Спасибо.
Боюсь что я не знаю причин такого поведения. Да и впрочем уже не особо помню особенности работы с картинками в php. Поэтому боюсь вам прийдется самому искать причину проблемы(
PS: как решите — отпишитесь тут пожалуйста)
юзайте imagecreatefrompng !! и памяти в 2р меньше жрет!!! качество чуть ниже.. хз. для аватарок самое то!
А у меня в гифе, после этого преобразования остаётся только первый кадр.
Так и должно быть?
Нет. Анимированные гифки должны обрабатываться немного иначе. Погуглите про ресайз анимированных гифок. Я с такой задачей не сталкивался, не смогу помочь.
Всё равно спасибо. Много у вас взял.
СПАСИБО!!! То, что надо))
Болшое спасибо! Ваша статья мне помогла.
if($out_extd == «gif»)
{
$color = imagecolorallocate($image_new, 256, 256, 256);
/// 256 — несуществующее значение цвета. У меня работает вроде норм. Не приходится выбирать цвет «жертвоприношения». Может я что-то конечно не учитываю.
imagecolortransparent($image_new, $color);
imagefill($image_new, 0, 0, $color);
}