9 years ago

Hello there,
i made a function to create a gradient image.

description:
gradient(int image_width, int image_height,
int start_red, int start_green, int start_blue,
int end_red, int end_green, int end_blue,
bool vertical)

function:
function gradient ($image_width , $image_height , $c1_r , $c1_g , $c1_b , $c2_r , $c2_g , $c2_b , $vertical = false )
{
// first: lets type cast;
$image_width = (integer) $image_width ;
$image_height = (integer) $image_height ;
$c1_r = (integer) $c1_r ;
$c1_g = (integer) $c1_g ;
$c1_b = (integer) $c1_b ;
$c2_r = (integer) $c2_r ;
$c2_g = (integer) $c2_g ;
$c2_b = (integer) $c2_b ;
$vertical = (bool) $vertical ;

// create a image
$image = imagecreatetruecolor ($image_width , $image_height );

// make the gradient
for($i = 0 ; $i < $image_height ; $i ++)
{
$color_r = floor ($i * ($c2_r - $c1_r ) / $image_height )+ $c1_r ;
$color_g = floor ($i * ($c2_g - $c1_g ) / $image_height )+ $c1_g ;
$color_b = floor ($i * ($c2_b - $c1_b ) / $image_height )+ $c1_b ;

$color = ImageColorAllocate ($image , $color_r , $color_g , $color_b );
imageline ($image , 0 , $i , $image_width , $i , $color );
}

# Prints out all the figures and picture and frees memory
header ("Content-type: image/png" );

if($vertical ){ $image = imagerotate ($image , 90 , 0 );}
ImagePNG ($image );
imagedestroy ($image );
}
?>

7 years ago

In case your script is using output-buffering-functions somewhere, then you have to clear the buffer first (with ob_clear()), before outputting an image with a function like imagepng().

And you should make sure that no buffer will get send after outputing an image by using the ob_end_flush()-function.

Furthermore you should check if a buffer has already been flushed somewhere before. This can be done using the headers_sent()-function.

Here is the full solution:

if(headers_sent ()){
die("Headers have been send somewhere within my script" );
}

Ob_clean (); //Clears the buffer

Header ("Content-type: image/png" );
imagepng ($img , NULL , 0 , NULL );

Ob_end_flush (); //Now we send the header and image plus we make sure that nothing will get send from now on (including possible shutdown-functions and __destruct()-methods) till the end of page-execution
?>

10 years ago

You know, maybe this goes without saying, but I thought I would drop a note in here. When developing code to resize images, it is best not to use GD. When using the current GD methodologies, you are reading content from an image and manipulating it. By then writing that content to a brand new file, you are losing the EXIF data.

For purposes when you want to retain EXIF data, it is recommended that you compile in and use the PECL Imagick extension. It has great resizing methods built right in and the EXIF data is retained.

10 years ago

I have been looking to send the output from GD to a text string without proxying via a file or to a browser.

I have come up with a solution.

This code buffers the output between the ob_start() and ob_end() functions into ob_get_contents()

See the example below

// Create a test source image for this example
$im = imagecreatetruecolor (300 , 50 );
$text_color = imagecolorallocate ($im , 233 , 14 , 91 );
imagestring ($im , 1 , 5 , 5 , "A Simple Text String" , $text_color );

// start buffering
ob_start ();
// output jpeg (or any other chosen) format & quality
imagejpeg ($im , NULL , 85 );
// capture output to string
$contents = ob_get_contents ();
// end capture
ob_end_clean ();

// be tidy; free up memory
imagedestroy ($im );

// lastly (for the example) we are writing the string to a file
$fh = fopen ("./temp/img.jpg" , "a+" );
fwrite ($fh , $contents );
fclose ($fh );
?>

Введение:

Возможности PHP не ограничиваются созданием HTML"я. PHP можно использовать для создания и манипулирования изображениями различных форматов, среди которых gif, png, jpg, wbmp, and xpm. Кроме того PHP способен выводить поток изображения прямиком в браузер. Для работы вам понадобится PHP собранный с графической библиотекой GD . GD и PHP могут зависеть и от других библиотек, в зависимости от того, с какими форматами изображений вы будете работать.

При помощи расширения EXIF вы сможете обрабатывать информацию хранящуюся в заголовках JPEG и TIFF изображений. При помощи него вы сможете получить доступ к мета тегам генерируемым цифровыми камерами. Для работы EXIF расширения библиотека GD не требуется.

Об установке и настройке библиотеки можно прочесть в официальной документации . Вероятней всего вам это не понадобится потому что у вас всё уже давно установлено.

С чем имеем дело

Функции обработки с изображениями в PHP я поделю на две категории. Те что работают с файлами и на те что работают с изображением в памяти(ресурсом).

Функций этих достаточно много, и все они важны. Описывать каждую я не буду, тем более лучше официальной документации мне этого не сделать. Но посмотреть на них с примерами мы вполне сможем, и сейчас же этим займёмся.

Сохранение обработанного изображение осуществляется при помощи
Описание гласит imagegif - Output image to browser or file. Для того чтобы примерно понять как это работает, нам понадобится редактор и картинка. Открываем картинку редактором и видим кучу непонятных символов.
Отлично, это наша картинка. Эта функция превращает пиксели в нашей памяти вот в такую запись. Для того чтобы записать всё это в файл указываем в imagegif 2 параметра.Указатель на наше изображение в памяти и название файла.
imagegif($im,"image.gif");

Когда браузер показывает нам какую нибудь картинку, он по сути запрашивает с сервера эти же каракули и превращает их на экране в изображение.
Значит мы можем не только сохранить эти каракули в файл, но и сразу отправить их браузеру. Делает это той-же самой imagegif, но на этот раз без второго параметра.
imagegif($im)

Но есть одно но. Нужно подсказать браузеру что это нужно обработать как картинку, а не как обычный текст. Напомню здесь о заголовках, рассматривавшихся в главе "Где работает php".
Для того чтобы отправить заголовок мы используем функцию header
содержание заголовка будем (тип документа: картинка гиф)
выглядит это вот так: header("Content-type: image/gif");

Ну и наконец последнее что вы встречали тут это
Переменную $img указатель она не удаляет, но память на которую переменная указывает она очищает. Пригодится если приспичит поработать с изображениями большой резолюции к примеру, с несколькими изображениями параллельно и при этом не выйти за рамки дозволенной свободной памяти.

Напомню что вся память выделенная скрипту во время выполнения автоматически освобождается с завершением его работы.

Ну вот и всё, теоретическую основу мы освоили..


Типичные ошибки:

Fatal error: Call to undefined function ...
Это значит что у тебя не установлена библиотека GD или всё гораздо проще - ты опечатался в названии функции. Установка GD описывается в документации. В денвере она есть по умолчанию, в убунту вам может пригодится команда sudo apt-get install php5-gd и перезапуск сервера.

Warning: imagecreatefrom..(a.png) failed to open stream: Permission denied
тут всё тоже просто. Правой кнопочкой по нашему изображению и выставьте права на чтение всем.

На экран выводится Resource id # вместо картинки.
=(Я же уже говорил, что вывод в браузер осуществляется при помощи специальных функций вида imagegif() без второго параметра. А у вас где то стоит echo $img; и на экран выводится не содержимое памяти, а просто сообщение что это указатель.

Warning: ...(): supplied argument is not a valid Image resource
Ожидался указатель а то что вы ему передали это строка или что-то другое. Причём если ты уверен что передаёшь указатель, но всё равно получаешь эту ошибку, то перестань припираться. Ты ошибаешься и лучше всё перепроверь . Сэкономит много нервов =)

�PNG ��� IHDR�����������h6��PIDAT(����JA��Mv�M4��
Ага, почему то выводится бинарный код изображение а браузер не думал сделать из него изображение, а обработал как обычный текст. Ну-ка пролистай еще раз весь урок и поищи ту часть в которой мы отправляли изображение браузеру.


Пару задачек для усвоения материала

Еще раз напомню этапы программирования
1. Постановка задачи
2. Определение требуемых операций
3. Подбор подходящих функций для каждой операции ()
4. Построение цепочки
5. Отладка с возвращением к первому пункту

Запомни - ты сейчас не получаешь опыт, а учишься его набирать. Думай об этом когда выполняешь задания.
Если ты их выполнил но не усвоил для себя ничего нового, то либо ты их плохо выполнил, либо ты их вообще не выполнил. Вряд ли при другом раскладе ты до сюда дочитал бы.

Задачки

1. Наложение печати
Уверен дописать на картинке пару слов у тебя труда не составит. Поэтому попробуем пририсовать графическую подпись. Открываем пэинт и делаем свой автограф. Получилось? Отлично.
Теперь сохраняем из гугла две фотки Памеллы Андерсон и пририсовываем к ним свою подпись.
(Подсказка: наверное полезно знать что у изображения есть такой параметр как Альфа - обозначающее прозрачность. Уверен Ctrl+F по списку функций что нибудь бросит вам в глаза, хотя это вовсе не обязательно.)

2. Captcha
Сложную мы делать не будем, сделаем простую. Каждый раз выводится случайные 4 цифры.
В чём прикол? Для этого будем использоваться особый шрифт - . А еще после того как наше изображение будет белого цвета с чёрным текстом, мы применим к нему фильтр негатива и на экране сделаем так чтобы оно отображалось наоборот, с чёрным фоном и белым цветом и оба изображение выводились на экран.(И всё в одном скрипте)

3. Логотип php
Это задание попроще и подлиннее - нарисуйте логотип php.

Ответы на эти вопросы вы найдете здесь:

<<< Назад Содержание Вперед >>>
Есть еще вопросы или что-то непонятно - добро пожаловать на наш

Уважаемые участники проекта ДвижКод! Сегодня вас ждёт еще одна интересная тема. Все мы привыкли, скажем, загружать новые аватарки в социальных сетях. Но как они масштабируются до нужных размеров? Что происходит на сервере? На эти и многие другие вопросы ответы вас ждут под катом.

Да, если кто не в курсе, то в интернет-терминологии «кат» (от англ. cut - резать), это место, которое разделяет новость на краткое (в общем списке) и полное представления.

Многие знают, что я разрабатываю собственную систему управления сайтами под названием Когир (Cogear, http://cogear.ru). История сложная и длинная, но упорно иду к поставленной цели.

Давайте посмотрим базовые аспекты работы с изображениями.

В PHP есть встроенная библиотека GD. Вот её документация:

http://php.net/manual/ru/book.image.php

На этой странице вы найдете огромное количество самых разных функций. Но не пугайтесь. Нас интересуют вполне конкретные.

Как видите, работа с изображениями пока что происходит в виде процедур (функций), поэтому объектно-ориентированную оболочку приходится писать самому.

Простейший пример работы с изображением:

Пробуем на локальной машине.

Создаём файл image.php (у меня для экспериментов локальный домен test.local ).

Кладём в корень папки сайта test.local картинку 1.jpg:

Вызываем в браузере http://test.local/image.php (в вашем случае - другой адрес).

Обнаруживаем в папке картинку 2.jpg:

Пропорции не сохранились, как видите. Потому что мы их не пересчитали.

Исправим наш скрипт так, чтобы происходил пересчёт:

$ratio_orig) { $width = $height*$ratio_orig; } else { $height = $width/$ratio_orig; } // перемещаем изображение из файла на полотно с изменением масштаба imagecopyresampled($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig,$height_orig); // вывод imagejpeg($image_p, "2.jpg", 100);

Посмотрим на результат:

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

Что еще можно делать с изображением? Подрезать его, поворачивать, наслаивать одно на другое, рисовать на нём.

Уверен, что вам будет интересен мой класс для работы с изображениями. Он вне системы не работает, так как является драйвером для шестерёнки (модуля) «Изображения».

Но вы найдете в нём для себя много интересного:

* @copyright Copyright (c) 2012, Беляев Дмитрий * @license http://cogear.ru/license.html * @link http://cogear.ru */ class Image_Driver_GD extends Image_Driver_Abstract { /** * Создает изображение */ public function create() { switch ($this->info->type) { case IMAGETYPE_JPEG: case IMAGETYPE_JPEG2000: $this->source = imagecreatefromjpeg($this->info->file); break; case IMAGETYPE_PNG: $this->source = imagecreatefrompng($this->info->file); imagecolortransparent($this->source, imagecolorallocate($this->source, 0, 0, 0)); imagealphablending($this->source, FALSE); imagesavealpha($this->source, TRUE); break; case IMAGETYPE_GIF: $this->source = imagecreatefromgif($this->info->file); imagecolortransparent($this->source, imagecolorallocate($this->source, 0, 0, 0)); imagealphablending($this->source, FALSE); imagesavealpha($this->source, TRUE); break; case IMAGETYPE_ICO: $this->source = imagecreatefromwbmp($this->info->file); break; } } /** * Уничтожает изображение */ public function destroy() { imagedestroy($this->source); is_resource($this->target) && imagedestroy($this->target); } /** * Создает целевое изображение * * @param mixed $width * @param mixed $height */ public function createTarget($width, $height) { $this->target = imagecreatetruecolor($width, $height); if ($this->info->type == IMAGETYPE_PNG OR IMAGETYPE_GIF == $this->info->type) { imagecolortransparent($this->target, imagecolorallocate($this->target, 0, 0, 0)); imagealphablending($this->target, FALSE); imagesavealpha($this->target, TRUE); } return $this->target; } /** * Изменяет масштаб изображения * * @param int|string $width Ширина * @param int|string $height Высота * @param string $fit Тип масштабирования width, height, fit * @param int|string $scale Какие изображения масштабировать any, up, down * @return object Изображение */ public function resize($width, $height, $fit = "width", $scale = "any") { $source_width = $this->smartSize($width, "width"); $source_height = $this->smartSize($height, "height"); // Проверяем тип масштабирования switch ($fit) { // Если подравниваем по ширине, то приводим высоту к нужным пропорциям case "width": $width = $source_width; $height = round(($source_width * $this->info->height) / $this->info->width); break; // Если подравниванием по высоте, то приводим ширину к нужным пропорциям case "height": $width = round(($this->info->width * $source_height) / $this->info->height); $height = $source_height; break; case "crop": if ($this->info->width > $this->info->height) { $width = round(($this->info->width * $source_height) / $this->info->height); $height = $source_height; } else { $width = $source_width; $height = round(($this->info->height * $source_width) / $this->info->width); } break; // По умолчанию просто растягиваем default: case "fit": } // Проверяем будет ли производиться масштабирование switch ($scale) { // Если изображение больше указанных размеров, с ним ничего не происходит case "up": if ($width < $this->info->width && $height < $this->info->height) { return $this; } break; // Если изображение меньше указанных размеров, с ним ничего не происходит case "down": if ($width > $this->info->width && $height > $this->info->height) { return $this; } break; case "any": default: // Ничего не делаем. Филоним } $this->target = $this->createTarget($width, $height); if (imagecopyresampled($this->target, $this->source, 0, 0, 0, 0, $width, $height, $this->info->width, $this->info->height)) { $this->source = $this->target; $this->info->width = $width; $this->info->height = $height; if("crop" == $fit){ return $this->crop("center","center" ,$source_width,$source_height); } } return $this; } /** * Производит обрезку изображения * * @param mixed $x Координата обрезки x * @param mixed $y Координата обрезки y * @param mixed $width Ширина обрезки * @param mixed $height Высотка обрезки * @return object Изображение */ public function crop($x, $y, $width, $height) { $x = $this->smartSize($x, "width") - $width/2; $y = $this->smartSize($y, "height") - $height/2; $width = $this->smartSize($width, "width"); $height = $this->smartSize($height, "height"); $this->target = $this->createTarget($width, $height); if (imagecopyresampled($this->target, $this->source, 0, 0, $x, $y, $width, $height, $width, $height)) { $this->source = $this->target; $this->info->width = $width; $this->info->height = $height; } return $this; } /** * Производит слияние изображений */ public function merge(Image $image, $x, $y, $percent = 100) { $x = $this->smartSize($x, "width"); $y = $this->smartSize($y, "height"); if (imagecopymerge($this->source, $image->getSource(), $x, $y, 0, 0, $image->object()->image->width, $image->object()->image->height, $percent)) { } return $this; } /** * Сохраняет изображение */ public function save($file = NULL, $options = array()) { $this->target OR $this->target = $this->source; if (strpos($file, ".") OR $file = $this->info->file) { $ext = pathinfo($file, PATHINFO_EXTENSION); $ext = strtolower($ext); } else { $ext = strtolower($file); $file = NULL; } switch ($ext) { case "jpg": case "jpeg": $options OR $options = 90; imagejpeg($this->target, $file, $options); break; case "gif": imagecolortransparent($this->target, imagecolorallocatealpha($this->target, 0, 0, 0, 127)); imagealphablending($this->target, FALSE); imagesavealpha($this->target, TRUE); imagegif($this->target, $file); break; case "png": imagealphablending($this->target, FALSE); imagesavealpha($this->target, TRUE); if (is_numeric($options)) { $quality = $options; $filters = PNG_NO_FILTER; } else { $quality = isset($options["quality"]) ? $options["quality"] : 9; $filters = isset($options["filters"]) ? $options["filters"] : PNG_NO_FILTER; } imagepng($this->target, $file, $quality, $filters); break; } $this->destroy(); } /** * Выводит изображение */ public function output($format, $options) { $this->save($format, $options); } }

Как он работает в системе? Ради чего столько кода?

Смотрите:

$image = new Image("1.jpg"); $image->resize(200,200,"width")->crop(200,200)->save("2,jpg);

Понимаете? Работает со всеми нужными форматами автоматически. Функционал при необходимости можно расширять.

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

В этой статье подробно разберём механизм загрузки изображений на сервер с помощью PHP не прибегая к сторонним компонентам и фреймворкам. Научимся безопасно загружать изображения не только с локальной машины пользователя, но и удалённые файлы по ссылке. Все примеры кода я буду писать в процедурном стиле, дабы вы быстрее могли читать код, а не перескакивать с одного метода на другой. Руководство полностью авторское и не претендует на какую-либо академичность изложения .

§1. Общие принципы

Всю последовательность загрузки изображения на сервер можно отобразить следующим образом: настройка php.ini получение файла проверка безопасности валидация данных сохранение на диск . Процесс загрузки картинки с компьютера пользователя или по URL ничем не отличаются, за исключением способа получения изображения и его сохранения. Общая схема загрузки картинки на сервер выглядит следующим образом:

Для валидации картинки по URL мы будем использовать функцию getimagesizefromstring() , т. к. cURL скачает её в переменную для дальнейших манипуляций.

Поскольку мы загружаем изображения на сервер, то хорошо было бы проверять их определённые параметры: ширину , высоту , тип картинки, размер файла в байтах. Это зависит от логики вашего приложения, но для наглядности в этом руководстве мы проверим все вышеописанные параметры.

§2. Правила безопасности

Безопасность загрузки изображений сводится к недопущению попадания на сервер чужеродного кода и его выполнения. На практике загрузка картинок наиболее уязвимое место в PHP-приложениях: попадание shell-скриптов , запись вредоносного кода в бинарные файлы , подмена EXIF-данных . Для того, чтобы избежать большинства методов взлома нужно придерживаться следующих правил:

а не доверять данным из $_FILES;
б не проверять MIME-тип картинки из функции getimagesize();
в загружаемому файлу генерировать новое имя и расширение;
г запретить выполнение PHP-скриптов в папке с картинками;
д не вставлять пользовательские данные через require и include;
е для $_FILES использовать is_uploaded_file() и move_uploaded_file().

Если есть чем дополнить «Правила безопасности», тогда оставляйте свои замечания или ссылки на статьи по безопасности в комментариях к этому руководству, а я опубликую их в этом параграфе.

§3. Конфигурация php.ini

PHP позволяет внести определённые конфигурационные значения в процесс загрузки любых файлов. Для этого необходимо в файле php.ini найти блоки «Resource Limits », «Data Handling » и «File Uploads », а затем отредактировать, по необходимости, следующие значения:

; Максимальное время выполнения скрипта в секундах max_execution_time = 60 ; Максимальное потребление памяти одним скриптом memory_limit = 64M ; Максимально допустимый размер данных отправляемых методом POST post_max_size = 5M ; Разрешение на загрузку файлов file_uploads = On ; Папка для хранения файлов во время загрузки upload_tmp_dir = home/user/temp ; Максимальный размер загружаемого файла upload_max_filesize = 5M ; Максимально разрешённое количество одновременно загружаемых файлов max_file_uploads = 10

Исходя из указанных значений, пользователь не сможет за один раз загрузить больше десяти файлов, причём каждый файл не должен превышать 5 Мбайт. Параметры из блока «Resource Limits » больше нужны для загрузки удалённого файла, т. к. с помощью cURL мы будем скачивать содержимое в переменную и проверять её по нужным нам критериям, а для этого необходимо дополнительное время и память.

Конфигурационный файл php.ini всегда необходимо настраивать согласно бизнес-логики разрабатываемого веб-приложения. Например, мы планируем загружать не более десяти файлов до 5 Мбайт, а это значит нам понадобиться ~50 Мбайт памяти. Кроме того, нам нужно знать максимальное время загрузки одного файла с локальной машины и по ссылке, дабы установить достаточное время выполнения скрипта в max_execution_time и не пугать пользователей ошибками.

§4. Загрузка картинок из формы

Сейчас мы не будем рассматривать загрузку нескольких файлов на сервер, а разберём лишь саму механику загрузки на примере одного файла. Итак, для загрузки картинки с компьютера пользователя необходимо с помощью HTML-формы отправить файл PHP-скрипту методом POST и указать способ кодирования данных enctype="multipart/form-data" (в данном случае данные не кодируются и это значение применяется только для отправки бинарных файлов). С формой ниже мы будем работать дальше:

Для поля выбора файла мы используем имя name="upload" в нашей HTML-форме, хотя оно может быть любым. После отправки файла PHP-скрипту file-handler.php его можно перехватить с помощью суперглобальной переменной $_FILES["upload"] с таким же именем, которая в массиве содержит информацию о файле:

Array ( => picture.jpg // оригинальное имя файла => image/jpeg // MIME-тип файла => home\user\temp\phpD07E.tmp // бинарный файл => 0 // код ошибки => 17170 // размер файла в байтах )

Не всем данным из $_FILES можно доверять: MIME-тип и размер файла можно подделать, т. к. они формируются из HTTP-ответа, а расширению в имени файла не стоит доверять в силу того, что за ним может скрываться совершенно другой файл. Тем не менее, дальше нам нужно проверить корректно ли загрузился наш файл и загрузился ли он вообще. Для этого необходимо проверить ошибки в $_FILES["upload"]["error"] и удостовериться, что файл загружен методом POST с помощью функции is_uploaded_file() . Если что-то идёт не по плану, значит выводим ошибку на экран.

// Перезапишем переменные для удобства $filePath = $_FILES ["upload" ]["tmp_name" ]; $errorCode = $_FILES ["upload" ]["error" ]; // Проверим на ошибки if ($errorCode !== UPLOAD_ERR_OK || ! is_uploaded_file ($filePath )) { // Массив с названиями ошибок $errorMessages = [ UPLOAD_ERR_INI_SIZE => "Размер файла превысил значение upload_max_filesize в конфигурации PHP." , UPLOAD_ERR_FORM_SIZE => "Размер загружаемого файла превысил значение MAX_FILE_SIZE в HTML-форме." , UPLOAD_ERR_PARTIAL => "Загружаемый файл был получен только частично." , UPLOAD_ERR_NO_FILE => "Файл не был загружен." , UPLOAD_ERR_NO_TMP_DIR => "Отсутствует временная папка." , UPLOAD_ERR_CANT_WRITE => "Не удалось записать файл на диск." , UPLOAD_ERR_EXTENSION => "PHP-расширение остановило загрузку файла." , ]; // Зададим неизвестную ошибку $unknownMessage = "При загрузке файла произошла неизвестная ошибка." ; // Если в массиве нет кода ошибки, скажем, что ошибка неизвестна $outputMessage = isset ($errorMessages [$errorCode ]) ? $errorMessages [$errorCode ] : $unknownMessage ; // Выведем название ошибки die ($outputMessage ); }

Для того, чтобы злоумышленник не загрузил вредоносный код встроенный в изображение, нельзя доверять функции getimagesize() , которая также возвращает MIME-тип. Функция ожидает, что первый аргумент является ссылкой на корректный файл изображения . Определить настоящий MIME-тип картинки можно через расширение FileInfo . Код ниже проверит наличие ключевого слова image в типе нашего загружаемого файла и если его не окажется, выдаст ошибку:

// Создадим ресурс FileInfo $fi = finfo_open (FILEINFO_MIME_TYPE); // Получим MIME-тип $mime = (string) finfo_file ($fi , $filePath ); );

На данном этапе мы уже можем загружать абсолютно любые картинки на наш сервер, прошедшие проверку на MIME-тип, но для загрузки изображений по определённым характеристикам нам необходимо валидировать их с помощью функции getimagesize() , которой скормим сам бинарный файл $_FILES["upload"]["tmp_name"] . В результате мы получим массив максимум из 7 элементов :

Array ( => 1280 // ширина => 768 // высота => 2 // тип => width="1280" height="768" // аттрибуты для HTML => 8 // глубина цвета => 3 // цветовая модель => image/jpeg // MIME-тип )

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

// Результат функции запишем в переменную $image = getimagesize ($filePath ); $limitBytes = 1024 * 1024 * 5 ; $limitWidth = 1280 ; $limitHeight = 768 ; // Проверим нужные параметры if (filesize ($filePath ) > $limitBytes ) die ("Размер изображения не должен превышать 5 Мбайт." ); if ($image > $limitHeight ) die (); if ($image > $limitWidth ) die ();

После всех проверок мы можем с уверенностью переместить наш загружаемый файл в какую-нибудь папку с картинками. Делать лучше это через функцию move_uploaded_file() , которая работает в безопасном режиме. Перед перемещением файла нельзя забыть сгенерировать случайное имя и расширение из типа изображения для нашего файла. Вот так это выглядит:

// Сгенерируем новое имя файла на основе MD5-хеша $name = md5_file ($filePath ); // Сократим.jpeg до.jpg // Переместим картинку с новым именем и расширением в папку /pics if (! move_uploaded_file ($filePath , __DIR__ . "/pics/" . $name . $format )) { die ("При записи изображения на диск произошла ошибка." ); }

На этом загрузка изображения завершена. Для более удобной загрузки файлов можете использовать класс UploadedFile из пакета Symfony HttpFoundation , который является обёрткой для $_FILES и также сохраняет файл через move_uploaded_file() .

§5. Загрузка изображения по ссылке

Для загрузки изображения по ссылке нам понадобиться библиотека cURL , которая работает с удалёнными ресурсами. С помощью неё мы скачаем контент в переменную. С одной стороны может показаться, что для этих целей подойдёт file_get_contents() , но на самом деле мы не сможем контролировать объём скачиваемых данных и нормально обрабатывать все возникшие ошибки. Для того, чтобы cURL корректно скачал данные нам нужно: разрешить следовать перенаправлениям , включить проверку сертификата , указать максимальное время работы cURL (формируется за счёт объёма скачиваемых данных и средней скорости работы с ресурсом). Как правильно скачать файл в переменную показано ниже с необходимыми параметрами :

// Каким-то образом получим ссылку $url = "https://site.ru/picture.jpg" ; // Проверим HTTP в адресе ссылки if (! preg_match ("/^https?:/i" , $url ) && filter_var ($url , FILTER_VALIDATE_URL)) { die ("Укажите корректную ссылку на удалённый файл." ); } // Запустим cURL с нашей ссылкой $ch = curl_init ($url ); // Укажем настройки для cURL curl_setopt_array ($ch , [ // Укажем максимальное время работы cURL CURLOPT_TIMEOUT => 60 , // Разрешим следовать перенаправлениям CURLOPT_FOLLOWLOCATION => 1 , // Разрешим результат писать в переменную CURLOPT_RETURNTRANSFER => 1 , // Включим индикатор загрузки данных CURLOPT_NOPROGRESS => 0 , // Укажем размер буфера 1 Кбайт CURLOPT_BUFFERSIZE => 1024 , // Напишем функцию для подсчёта скачанных данных // Подробнее: http://stackoverflow.com/a/17642638 CURLOPT_PROGRESSFUNCTION => function ($ch , $dwnldSize , $dwnld , $upldSize , $upld ) { // Когда будет скачано больше 5 Мбайт, cURL прервёт работу if ($dwnld > 1024 * 1024 * 5 ) { return - 1 ; } }, // Включим проверку сертификата (по умолчанию) CURLOPT_SSL_VERIFYPEER => 1 , // Проверим имя сертификата и его совпадение с указанным хостом (по умолчанию) CURLOPT_SSL_VERIFYHOST => 2 , // Укажем сертификат проверки // Скачать: https://curl.haxx.se/docs/caextract.html CURLOPT_CAINFO => __DIR__ . "/cacert.pem" , ]); $raw = curl_exec ($ch ); // Скачаем данные в переменную $info = curl_getinfo ($ch ); // Получим информацию об операции $error = curl_errno ($ch ); // Запишем код последней ошибки // Завершим сеанс cURL curl_close ($ch );

Если всё прошло успешно и cURL уложился в 60 секунд, тогда содержимое по ссылке будет скачано в переменную $raw . Кроме того, функция curl_getinfo() вернёт информацию о проделанном запросе, откуда мы можем получить дополнительную информацию для анализа работы с удалёнными ресурсами:

Array ( => image/jpeg // MIME-тип из Content-Type => 200 // последний HTTP-код => 0 // количество перенаправлений => 0.656 // общее время работы cURL => 0.188 // время на соединение с хостом => 4504 // реальный размер полученных данных => 4504 // размер данных из Content-Length /* ... */ )

Дальше нам нужно проверить нет ли ошибок в curl_errno() и удостовериться, что ресурс отдаёт HTTP-код равный 200, иначе мы скажем, что по такому-то URL ничего не найдено. После всех проверок переменную $raw передаём в getimagesizefromstring() и работаем уже по отработанной схеме как в случае с загрузкой картинок из формы.

Обратите внимание, что мы валидируем размер файла в момент получения данных, т. к. мы не можем на 100% доверять curl_getinfo(), поскольку значения content_type, http_code, download_content_length формируются на основе полученных HTTP-заголовков. Скачивать файл полностью, а потом проверять количество байт потребует много времени и памяти. Поэтому мы контролировали размер получаемых данных с помощью опции CURLOPT_PROGRESSFUNCTION: как только cURL получит больше данных, чем наш лимит, он прекратит работу и выдаст ошибку CURLE_ABORTED_BY_CALLBACK.

// Проверим ошибки cURL и доступность файла if ($error === CURLE_OPERATION_TIMEDOUT) die ("Превышен лимит ожидания." ); if ($error === CURLE_ABORTED_BY_CALLBACK) die ("Размер не должен превышать 5 Мбайт." ); if ($info ["http_code" ] !== 200 ) die ("Файл не доступен." ); // Создадим ресурс FileInfo $fi = finfo_open (FILEINFO_MIME_TYPE); // Получим MIME-тип используя содержимое $raw $mime = (string) finfo_buffer ($fi , $raw ); // Закроем ресурс FileInfo finfo_close ($fi ); // Проверим ключевое слово image (image/jpeg, image/png и т. д.) if (strpos ($mime , "image" ) === false ) die ("Можно загружать только изображения." ); // Возьмём данные изображения из его содержимого $image = getimagesizefromstring($raw ); // Зададим ограничения для картинок $limitWidth = 1280 ; $limitHeight = 768 ; // Проверим нужные параметры if ($image > $limitHeight ) die ("Высота изображения не должна превышать 768 точек." ); if ($image > $limitWidth ) die ("Ширина изображения не должна превышать 1280 точек." ); // Сгенерируем новое имя из MD5-хеша изображения $name = md5 ($raw ); // Сгенерируем расширение файла на основе типа картинки $extension = image_type_to_extension ($image ); // Сократим.jpeg до.jpg $format = str_replace ("jpeg" , "jpg" , $extension ); // Сохраним картинку с новым именем и расширением в папку /pics if (! file_put_contents (__DIR__ . "/pics/" . $name . $format , $raw )) { die ("При сохранении изображения на диск произошла ошибка." ); }

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

§6. Настройка выбора нескольких файлов

В этом параграфе разберём способы загрузки нескольких изображений за один раз с локальной машины пользователя и по удалённым ссылкам. Для отправки ссылок мы задействуем $_POST и передадим ей все данные с помощью тега textarea . Для загрузки файлов из формы мы продолжим дальше работать с $_FILES . Наша новая HTML-форма будет немного отличаться от старой.

В конец имени поля выбора файла name="upload" добавились фигурные скобки и аттрибут multiple , который разрешает браузеру выбрать несколько файлов. Все файлы снова загрузятся во временную папку, если не будет никаких ошибок в php.ini . Перехватить их можно в $_FILES , но на этот раз суперглобальная переменная будет иметь неудобную структуру для обработки данных в массиве. Решается эта задача небольшими манипуляциями с массивом:

// Изменим структуру $_FILES foreach ($_FILES ["upload" ] as $key => $value ) { foreach ($value as $k => $v ) { $_FILES ["upload" ][$k ][$key ] = $v ; } // Удалим старые ключи unset ($_FILES ["upload" ][$key ]); } // Загружаем все картинки по порядку foreach ($_FILES ["upload" ] as $k => $v ) { // Загружаем по одному файлу $_FILES ["upload" ][$k ]["tmp_name" ]; $_FILES ["upload" ][$k ]["error" ]; }

Для загрузки нескольких картинок по URL передадим наши ссылки через textarea с именем name="upload" , где их можно указать через пробел или с новой строки. Функция preg_split разберёт все данные из $_POST["upload"] и сформирует массив, по которому нужно пройтись циклом и каждый валидный URL отправить в обработчик.

$data = preg_split ("/\s+/" , $_POST ["upload" ], - 1 , PREG_SPLIT_NO_EMPTY); foreach ($data as $url ) { // Валидируем и загружаем картинку по URL }