Работа с изображениями.

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
jdiond
Сообщения: 30
Зарегистрирован: 2013.11.02, 23:50

Работа с изображениями.

Сообщение jdiond »

Есть 2 вопроса.

Первая задача:
Описание:
Имеются Новости, Статьи и т.д. - то есть, таблицы news, articles, ... . У новостей должно быть 3 изображения + alt.
Хранить собираюсь так:
/path/to/vasja.png;
/path/to/vasja-500x500.png;
/path/to/vasja-100x100.png;
alt = Вася МегоМозг;

Вопросы:
1) Создавать таблицу images с 4мя этими полями, а в таблицах news,articles хранить id изображения или есть инной разумный вариант?
2) Как правильно обрабатывать, сохранять и вообще работать с изображениями? В моделе, контроллере или писать что-то отдельное для этого дела? (вопрос общий)

Вторая задача:
Описание:
Тут все страшно.
Думаю тут есть люди которые сидят в ВК и загружали аватарку. Так вот, мне нужен функционал такого же загрузчика как в ВК.
То есть, по шагам:
1) грузим фотку
2) выделяем и сохраняем область фотки для аватарки, то есть метод обрезания (crop).
3) подгружаеться уже обрезанная фотка и так же, выделям и сохраням область фотки под миниатюру (так же метод crop).

Как это сейчас у меня выглядет.
Заходим в админку, нажимаем создать новость, среди всех полей есть поле изображение. Нажимаем, грузим изображение, после загрузки всплывает модальное окно и 2 раза делаеться обрезание. http://joxi.ru/buxrU_3JTJA5U5nG76I
Функционал такой:
Грузим фото, по событию изменения кнопки загрузки файла, js вызывает метод контроллера (отдельного для этого), который обрабатывает файл (проверяет, существует ли имя, создает папку, если нужна и т.д/сохраняет файл), этот метод возвращает js-ту имя изображения, после этого всплывает модальное окно в котором делаем обрезание, жмем сохранить - опять летит методу контроллера, который обрезает/сохраняет и возвращает путь обрезанного файла и так 2 раза с обрезанием.
И тут такие моменты:
1) Изображения уже загружены, а новость еще не сохранена, вдруг пользователь отменит создание.
2) Как сохранить после этих операций имена всех изображений в базу не представляю. По сабмиту будет читать value поля, в которое изначально попало оригинальное изображение. А у нас первый метод по загрузке может изменить имя этого файла, если такой уже существует.
Описал, теперь кодом.

FileController.php

Код: Выделить всё

namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\web\UploadedFile;

use yii\helpers\Inflector;
use yii\helpers\FileHelper;
use yii\imagine\Image;
use Imagine\Image\ImageInterface;

class FileController extends Controller
{
    public $file;
    public $fileName;
    public $fileExt;
    public $path = 'uploads/';

    private function upload($path='images')
    {
        $this->path = FileHelper::normalizePath($this->path.$path).DIRECTORY_SEPARATOR;
        if (!file_exists($this->path)) {
            FileHelper::createDirectory($this->path);
        }
        $this->file = UploadedFile::getInstanceByName('file');
        $this->fileName = Inflector::slug($this->file->basename);
        $this->fileExt = $this->file->extension;
        if(file_exists($this->path.$this->fileName . '.' . $this->fileExt)) {
            $this->fileName .= '-' . rand(10000,99999);
        }

        return $this->fileName . '.' . $this->fileExt;
    }
    private function resize($width, $height)
    {
        Image::thumbnail(
            $this->path . $this->fileName . '.' . $this->fileExt,
            $width, 
            $height, 
            ImageInterface::THUMBNAIL_INSET
        )->save(
            $this->path . $this->fileName . '-' . $width . 'x' . $height . '.' . $this->fileExt,
            ['quality' => 100]
        );

        return $this->fileName . '-' . $width . 'x' . $height . '.' . $this->fileExt;
    }
    private function crop($file, $width, $height , $x, $y)
    {
        Image::crop(
            $this->path . $file,
            $width,
            $height, 
            [$x, $y]
        )->save(
            $this->path . $this->fileName . '-' . $width . 'x' . $height . '.' . $this->fileExt, 
            ['quality' => 100]
        );
        return $this->fileName . '-' . $width . 'x' . $height . '.' . $this->fileExt;
    }
    private function save()
    {
        $this->file->saveAs($this->path . $this->fileName . '.' . $this->fileExt);
    }

    public function actionAjaxUploadResize()
    {
        if(isset($_POST['path'])) {
            $path = $_POST['path'];
        } else {
            $path = 'images';
        }
        $this->upload($path);
        $this->save();

        $responce = Yii::$app->get('response');
        $responce->format = 'json';
        $responce->data = ['url' => Yii::$app->request->hostInfo . DIRECTORY_SEPARATOR . $this->path, 'file' => $this->resize(570,500), 'filename' => $this->fileName . '.' . $this->fileExt];
        $responce->send();
    }

    public function actionAjaxCrop()
    {
        if(isset($_POST)) {
            $path = $_POST['path'];
            $file = $_POST['file'];
            $filename = $_POST['filename'];
            $width = $_POST['width'];
            $height = $_POST['height'];
            $x = $_POST['x'];
            $y = $_POST['y'];
        }
        $info = pathinfo($filename);
        $this->fileName = $info['filename'];
        $this->fileExt = $info['extension'];
        $this->path = FileHelper::normalizePath($this->path.$path).DIRECTORY_SEPARATOR;

        $responce = Yii::$app->get('response');
        $responce->format = 'json';
        $responce->data = ['url' => Yii::$app->request->hostInfo . DIRECTORY_SEPARATOR . $this->path, 'file' => $this->crop($file, $width, $height, $x, $y), 'filename' => $this->fileName . '.' . $this->fileExt];
        $responce->send();
    }
}
 
fileUpload.js

Код: Выделить всё

var input = $('#news-file, #article-file');
var modal = $('#modal-image');
var imagePath;
	

input.on('change', function(){
	if(this.id == 'news-file') {
		imagePath = 'images/news';
	} else {
		imagePath = 'images/articles';
	}
	var file = ajaxUpload(this,imagePath);

	crop(file.filename, file.file, file.url + file.file, imagePath);
});


function ajaxUpload(input,imagePath)
{
	var formData = new FormData();
	var file = input.files[0];

	if (!file.type.match('image.*')) {
		return;
	}

	formData.append('file', file, file.name);
	formData.append('path', imagePath);

	return $.parseJSON($.ajax({
	    url: "/file/ajax-upload-resize",
	    type: "POST",
	    data: formData,
	    processData: false,
	    contentType: false,
   		async: false,
  	}).responseText);
}

function crop(filename, file, src, imagePath, mode=true)
{
	var data = '';
	var image = document.createElement('img');

	image.id = 'image';
	image.src = src;

	modal.find('.modal-body').html(image);
	if(mode==true) {
		modal.modal('show');
	}
	

    $('#image').Jcrop({
        bgColor:     'black',
        bgOpacity:   .4,
		minSize: [145,82],
        aspectRatio: 145/82,
        onChange: function(coords){
        	data = coords;
        }
    });
    $('#save-image').on('click', function(){
    	if(mode==false) {
    		modal.modal('hide');
    	}
		return $.ajax({
			url: '/file/ajax-crop',
			method: 'POST',
			data: {
				'filename': filename,
				'path': imagePath,
				'file': file,
				'x': data.x.toFixed(),
				'y': data.y.toFixed(),
				'width': data.w.toFixed(),
				'height': data.h.toFixed(),
			},
			success: function(data) {
				if(mode==true) {
					crop(data.filename, data.file, data.url + data.file, imagePath, false);
				}
			}
		});
	});
}
Чистоты тут нет, так как все и так надо переписывать.

Этот вопрос тесно связан с первым. Сначала надо бы разобраться как и где правильно обрабатывать загрузку файлов, а потом уже манипуляции вот такие проводить, но всеравно ответ на второй вопрос нужен. С нетерпением жду ответов. Заранее спасибо.
Shappy
Сообщения: 86
Зарегистрирован: 2013.09.19, 12:31

Re: Работа с изображениями.

Сообщение Shappy »

Вопросы:
1) Создавать таблицу images с 4мя этими полями, а в таблицах news,articles хранить id изображения или есть инной разумный вариант?
2) Как правильно обрабатывать, сохранять и вообще работать с изображениями? В моделе, контроллере или писать что-то отдельное для этого дела? (вопрос общий)
1) Если у новости будет только 3 картинки, т.е. vasja.png, vasja-500x500.png и vasja-100x100.png, то можно путь до картинки, расширение и альт впихнуть в таблицу новостей. Если нужна возможность нескольких картинок (6, 9 или более), то создавать отдельную таблицу, только создавать 4 поля не нужно. Можно создать 3 поля, Экономия будет;) например 'img_name', 'extention_id' (ссылка на таблицу расширений картинки), alt.
В итоге 3 картинки получишь так:

Код: Выделить всё

$img_name . $extention_name;
$img_name . '-500x500' . $extention_name;
$img_name . '-100x100' . $extention_name;
2) Я в моделе обрабатываю... Если где-то будет повторяться, нужно вынести конечно...

На следующие вопросы я бы ответил что имена файлов можно хранить в сессии... При загрузке удачной идет запись в сессию. При открытии страницы подачи новости удаляем из сессии сохраненные имена фотографий, предварительно удалив сами фото. Если пользователь закроет браузер, либо перейдет на левый источник, уже ничего не сделаешь... Поэтому сохраняем файлы сначала в tmp-директорию, и изредка ее чистим...
Может кто еще чего предложит... Я так делал:)
Аватара пользователя
vova07
Сообщения: 1004
Зарегистрирован: 2012.11.29, 14:52
Откуда: Chisinau, Moldova

Re: Работа с изображениями.

Сообщение vova07 »

1) Если заранее известно что у сущности будут только 3 картинки, и больше это количество не будет менятся я бы использовал таблицу сущности для хранения информации об ихображении. Если предпологается что в будущем количество будет менятся, то отдельная таблица лучше. В случае 3 картинок в таблице можно обойтись только одним дополнительным полем "image_name" которое хранила бы название основной картинки. Дальше в модели можно завести геттер "getImage($size)" и формировать полный путь до картинки, именуя мини-изображения по принципу "ширинаХвысота-название_изображения.расширение" => "500X500-image.png"

2) Что касается загрузки картинок и их сохранение: логику можете подсмотреть тут. Самая идея заключается в заполнении скрытого поля названием которое возвращает скрипт после загрузки картинки во временую папку. Ну а дальше перебор значений этих скрытых полей и их сохранение после успешного сохранения самой сущности. Ну а для того чтобы эту логику не дублировать каждый раз при необходимости, лучше было бы завернуть все это в поведении.

Готовая реализация функционала из вашего 2 пунтка можете подсмотреть тут. Код на гитхабе.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Работа с изображениями.

Сообщение zelenin »

еще интересный хак: можно геттер реализовать так

Код: Выделить всё

getImage( 'cat.jpg', 500, 400 );

function getImage( $name /* или $id */, $width, $height )
{
// проверяем существует ли миниатюра cat-500x500.jpg
// если не суещствует, проверяем существует ли оригинальная картинка $name cat.jpg
// ресайзим оригинал и отдаем урл до готовой миниатюры.
}
таким образом, не надо заранее заботиться обо всех размерах, а можно картинки получать налету.
использовал такой прием в одном старом проекте на yii1.
jdiond
Сообщения: 30
Зарегистрирован: 2013.11.02, 23:50

Re: Работа с изображениями.

Сообщение jdiond »

vova07 писал(а):Готовая реализация функционала из вашего 2 пунтка можете подсмотреть тут. Код на гитхабе.
как реализовать двоетапную загрузку изображения с кропом.
1. Сначала нужно загрузить оригинал изображения и сохранить его для возможности подальшего использования (для кропа)
2. Необходимо сделать кроп изображения для использования в самом материале
3. Кроп из результата кропа (п.2) для превюшки материала.

Также нужно иметь возможность "перекропить" оригинал изображения при правке материала, либо загрузить новое
Ответить