08 января

Crop on HTML5

Cейчас все больше вычислений стараются производить на стороне клиента, чтобы сократить нагрузку на сервер и увеличить производительность веб-приложения в целом. Я уже писал о сжатии изображений на стороне клиента, до его отправки на сервер; сегодня же речь пойдет о кадрировании изображения (инструмент crop) средствами HTML5 canvas.


Демо Скачать

Очень часто, регистрируясь на каком-либо сервисе, он подтягивает фото из соц. сети и предлагает выбрать необходимый фрагмент для использования его как аватарки. Реализуют пободный функционал обычно на HTML5 canvas, и в этот раз мы будем использовать также библиотеку fabricjs. Это отличный инструмент, который включает объектную модель для работы с canvas и ряд других инструментов, о которых вы можете почитать на официальном сайте проекта.

Работая в Adobe Photoshop, мы привыкли, что при кадрировании появляется рамка, а все, что вне ее, затемняется. Также в последних версиях появились направляющие линии, которые помогают выровнять объект. Предлагаю нечто подобное реализовать и для нашего кроупа: блоки, затемняющие все, кроме выделенной области и вспомогательные линии, которые по горизонтали и вертикали делят выделенную область на 3 равные части.

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

И перейдем непосредственно к реализации:

function init_image_helper(iw, ih, img){
	var bgcolor 		= '#000000', // Цвет затемнения
	opacity			= 0.7, // Прозрачность затемнения
	bordercolor		= 'rgba(155,155,155,0.8)', // Цвет рамок
	cornerColor		= 'rgba(155,155,155,0.6)', // Фон управляющих точек
	cornerStrokeColor	= 'rgba(100,100,100,1)', // Цвет рамок управляющих точек
	cornerSize		= 10, // Размер управляющих очек
	showHelpers		= true; // Показывать вспомогательные линии
	
	var src = img.attr('src');
	img.wrap('<div class="crop_wrap" />');
	
	// Создаем вспомогательные элементы
	var crop_area = img.parent();
	crop_area.html( '<div class="crop_controlls">'+
		'<button class="crop_ok">Готово</button> '+
		'<button class="crop_cancel">Отмена</button>'+
	'</div>'+
	'<canvas id="c"></canvas>');
	
	var cw = iw;
	var ch = ih;
	
	// Создаем холст
	var canvas = new fabric.Canvas('c', {
		width: cw,
		height: ch,
		backgroundColor: "#ffffff"
	});
	canvas.uniScaleTransform = true;
	
	// Добавляем изображение на холст
	var imgInstance;
	var fimg = fabric.Image.fromURL(src, function(imgInstance) {
		imgInstance.set({
			left: 0, 
			top: 0, 
			width: iw, 
			height: ih, 
			selectable: false,
			evented: false,
			lockMovementX: true,
			lockMovementY: true,
			lockRotation: true,
			lockScalingX: true,
			lockScalingY: true,
			lockUniScaling: true,
			hasControls: false,
			hasBorders: false
		});
		canvas.add(imgInstance);
		canvas.sendToBack(imgInstance);
	});
	
	var crop_on = true;
	
	var crop2 = new fabric.Rect({
		left: Math.ceil(cw/4),
		top: Math.ceil(ch/4),
		fill: '#ffffff',
		width: Math.ceil(cw/2),
		height: Math.ceil(ch/2),
		hasRotatingPoint: false,
		opacity:0,
		strokeWidth: 0,
		transparentCorners: false,
		borderColor: bordercolor,
		borderDashArray: [5, 5],
		cornerColor: cornerColor,
		cornerStrokeColor: cornerStrokeColor,
		cornerSize: cornerSize
	});
	
	var p = crop2;
	var x1 = Math.ceil(p.left);
	var x2 = Math.ceil(p.left+p.width);
	var y1 = Math.ceil(p.top);
	var y2 = Math.ceil(p.top+p.height);
	
	// Прямоугольники затемнения
	var crop_l = makecrop({left: 0, top: 0, width: x1, height: ch}),
		crop_r = makecrop({left: x2, top: 0, width: cw-x2, height: ch}),
		crop_t = makecrop({left: x1, top: 0, width: x2-x1, height: y1}),
		crop_b = makecrop({left: x1, top: y2, width:x2-x1, height: ch-y2});
	
	// Вспомогательные линии
	if (showHelpers){
		var crop_help_1 = make_line({x1: Math.ceil((x2-x1)/3)+x1, y1: y1, x2: Math.ceil((x2-x1)/3)+x1, y2: y2}),
			crop_help_2 = make_line({x1: Math.ceil((x2-x1)/3*2)+x1, y1: y1, x2: Math.ceil((x2-x1)/3*2)+x1, y2: y2}),
			crop_help_3 = make_line({x1: x1, y1: Math.ceil((y2-y1)/3)+y1, x2: x2, y2: Math.ceil((y2-y1)/3)+y1 });
			crop_help_4 = make_line({x1: x1, y1: Math.ceil((y2-y1)/3*2)+y1, x2: x2, y2: Math.ceil((y2-y1)/3*2)+y1 });
	}
	// helper_block;
	$('.crop_controlls').css({
		'top': y2-$('.crop_controlls').outerHeight()-10+'px',
		'left': x2-$('.crop_controlls').outerWidth()-10+'px'
	});
	
	// Добавляем объекты на холст
	canvas.add(crop2);
	canvas.add(crop_l);
	canvas.add(crop_r);
	canvas.add(crop_t);
	canvas.add(crop_b);
	
	if (showHelpers){
		canvas.add(crop_help_1);
		canvas.add(crop_help_2);
		canvas.add(crop_help_3);
		canvas.add(crop_help_4);
	}
	
	canvas.setActiveObject(crop2);
	
	canvas.on({
		'object:moving': onCroping,
		'object:scaling': onCroping
	});
	
	// Пересчитываем при изменении области
	function onCroping(e){
		var p = e.target,
			wi = Math.ceil(p.width * p.scaleX),
			hi = Math.ceil(p.height * p.scaleY);
			

		if (p.top < 0){
			p.set({ 'top': 0});
		}
		if (p.left < 0){
			p.set({ 'left': 0});
		}
		if (wi+p.left > cw){
			p.set({ 'left': cw-wi});
		}
		if (hi+p.top > ch){
			p.set({ 'top': ch-hi});
		}
		if (wi > cw){
			p.set({ 'scaleX': 1, 'width': cw, 'left': 0});
		}
		if (hi > ch){
			p.set({ 'scaleY': 1, 'height': ch, 'top': 0});
		}
		
		wi = Math.ceil(p.width * p.scaleX),
		hi = Math.ceil(p.height * p.scaleY);
		
		var x1 = Math.ceil(p.left);
		var x2 = Math.ceil(p.left+wi);
		var y1 = Math.ceil(p.top);
		var y2 = Math.ceil(p.top+hi);		
		
		crop_l.set({ 'width': x1});
		crop_r.set({ 'left': x2, 'width': cw-x2});
		crop_t.set({ 'left': x1, 'width': x2-x1, 'height': y1});
		crop_b.set({ 'left': x1, 'width': x2-x1, 'top': y2, 'height': ch-y2});
		
		if (showHelpers){
			crop_help_1.set({x1: Math.ceil((x2-x1)/3)+x1, y1: y1, x2: Math.ceil((x2-x1)/3)+x1, y2: y2}),
			crop_help_2.set({x1: Math.ceil((x2-x1)/3*2)+x1, y1: y1, x2: Math.ceil((x2-x1)/3*2)+x1, y2: y2}),
			crop_help_3.set({x1: x1, y1: Math.ceil((y2-y1)/3)+y1, x2: x2, y2: Math.ceil((y2-y1)/3)+y1 });
			crop_help_4.set({x1: x1, y1: Math.ceil((y2-y1)/3*2)+y1, x2: x2, y2: Math.ceil((y2-y1)/3*2)+y1 });
		}
		
		canvas.renderAll();
		
		// helper_block;
		$('.crop_controlls').css({
			'top': y2-$('.crop_controlls').outerHeight()-10+'px',
			'left': x2-$('.crop_controlls').outerWidth()-10+'px'
		});
	}
	
	function calc_crop(p){
		
	}
	
	// Обрезаем
	function makecrop(coords) {
		return new fabric.Rect({
			left: coords.left,
			top: coords.top,
			fill: bgcolor,
			width: coords.width,
			height: coords.height,
			selectable: false,
			evented: false,
			lockMovementX: true,
			lockMovementY: true,
			lockRotation: true,
			lockScalingX: true,
			lockScalingY: true,
			lockUniScaling: true,
			hasControls: false,
			hasBorders: false,
			opacity: opacity,
			strokeWidth: 0,
			hasBorder: false
		});
	}
	
	// Генерируем вспомогательную линию
	function make_line(coords){
		return new fabric.Line([coords.x1, coords.y1, coords.x2, coords.y2], {
			selectable: false,
			evented: false,
			lockMovementX: true,
			lockMovementY: true,
			lockRotation: true,
			lockScalingX: true,
			lockScalingY: true,
			lockUniScaling: true,
			hasControls: false,
			strokeDashArray: [5,5],
			stroke: bordercolor
		});
	}
			
	// применить
	$('.crop_ok').on('click', function(){
		
		if(crop_on){
			var x = crop2.left,
				y = crop2.top,
				width = crop2.width * crop2.scaleX,
				height = crop2.height * crop2.scaleY;
				
			canvas.remove(crop_t, crop_b, crop_r, crop_l, crop2);
			if (showHelpers){
				canvas.remove(crop_help_1, crop_help_2, crop_help_3, crop_help_4);
			}
		}

		canvas.deactivateAll();
		
		if (!fabric.Canvas.supports('toDataURL')) {
		  alert('This browser doesn\'t provide means to serialize canvas to an image');
		}else{
			var resultimg = canvas.toDataURL({
				format: 'jpeg',
				left: x,
				top: y,
				width: width,
				height: height
			});
		
			crop_area.html('<img src="'+resultimg+'" alt="" class="cropd" />');
		}
		
		return false;
	});
	
	// Отменить
	$('.crop_cancel').on('click', function(){
		crop_area.html('<img src="'+src+'" alt="" />');
		return false;
	});
}


Демо Скачать