Recortando imagens com jCrop e PHP

Quando desenvolvemos um sistema com upload de fotos envolvendo miniaturas, é muito comum um cliente não gostar dos thumbnails gerados automaticamente pelo PHP, já que todos devem possuir o corte nas mesmas coordenadas X e Y. Existem duas maneiras de resolver o problema: uma é criar um novo campo de upload para que o cliente já mande a miniatura no tamanho certinho. A outra é desenvolver uma interface em javascript para o cliente selecionar, após o upload, a área da foto que ele deseja utilizar na miniatura.

O plugin jCrop que apresento neste artigo facilita a implementação da segunda solução. Além de desenvolver o frontend, vou também mostrar a parte em PHP que redimensiona e recorta a imagem – tudo isso utilizando nossa classe m2brimagem.

Configurações iniciais

Como se trata de um plugin jQuery precisamos da chamada para a biblioteca em algum lugar de nosso HTML. Além disso, vamos precisar também incluir o arquivo fonte do jCrop com sua folha de estilo e imagens necessárias. Clique aqui para fazer o download dos arquivos.

<link href="css/jquery.Jcrop.css" rel="stylesheet" type="text/css" />
<script src="js/jquery.min.js"></script>
<script src="js/jquery.Jcrop.js"></script>

Desse jeito já dá pra utilizar o jCrop em sua forma mais básica:

<img src="imagem.jpg" width="634" height="340" id="jcrop" />
<script type="text/javascript">
$(function(){ 
	$('#jcrop').Jcrop(); 
});
</script>

No exemplo acima, ‘#jcrop’ é o atributo ID da imagem alvo do crop. Você pode utilizar qualquer tipo de seletor do jQuery (classes, sub-elementos etc.), aplicando o frontend do crop em vários elementos img.

Muito legal, mas e pra salvar a imagem? O plugin funciona apenas no cliente, na interface da aplicação. O crop mesmo tem que ser feito no servidor.

Processando o crop com PHP

Primeiramente vamos entender como funciona o jCrop. Seu método de marcação na imagem retorna um array com as dimensões e o posicionamento do crop. Ele possui alguns eventos, aqui vamos utilizar os dois principais: onChange e onSelect. O onChange executa uma função qualquer no momento que a marcação é alterada e o onSelect executa uma função qualquer no momento que a seleção está em andamento.

Sendo assim, vamos utilizar, por enquanto, a função exibePreview em ambos os casos. O que ela faz é atualizar uma pré-visualização do resultado final do crop, além de armazenar as variáveis para envio e processamento no servidor.

function exibePreview( c )
{
	// c.x, c.y, c.x2, c.y2, c.w, c.h
};

A função recebe o array c, aquele com as dimensões e coordenadas do crop. Os valores do array são:

w largura (width) do crop
h altura (height) do crop
x1 e x2 posições horizontais do crop na imagem
y1 e y2 posições verticais do crop na imagem

Note que é aí que termina o trabalho do jCrop. Os valores devem ser processados no PHP. No nosso exemplo, conforme mencionei anteriormente, a função exibePreview vai registrar as posições em inputs do tipo hidden no formulário de envio.

function exibePreview( c )
{
	// campos hidden que armazenam os valores
	$('#x').val(c.x);
	$('#y').val(c.y);
	$('#x2').val(c.x2);
	$('#y2').val(c.y2);
	$('#w').val(c.w);
	$('#h').val(c.h);
};

Além disso ela deve atualizar a pré-visualização da imagem recortada. Pra isso vamos precisar do tamanho original da imagem. Se você está usando o mesmo formato de imagem, basta utilizar os mesmos valores sempre. No nosso caso, como a imagem é enviada via formulário, utilizamos a função getimagesize do php para retornar a largura (índice 0 do array de retorno) e a altura (índice 1). Elas são necessárias para calcular o posicionamento do crop. A idéia é criar um div com as dimensões do crop, mascarando a imagem original.

function exibePreview(c)
{
	var rx = 100 / c.w;
	var ry = 100 / c.h;

	// atualiza CSS do preview para refletir o tamanho da imagem enviada 
	// e o posicionamento do crop
	$('#preview').css({
		width: Math.round(rx * <?php echo $imagesize[0]; ?>) + 'px',
		height: Math.round(ry * <?php echo $imagesize[1]; ?>) + 'px',
		marginLeft: '-' + Math.round(rx * c.x) + 'px',
		marginTop: '-' + Math.round(ry * c.y) + 'px'
	});

	// campos hidden que armazenam os valores
	$('#x').val(c.x);
	$('#y').val(c.y);
	$('#x2').val(c.x2);
	$('#y2').val(c.y2);
	$('#w').val(c.w);
	$('#h').val(c.h);
}

Com a função exibePreview completa, podemos agora atualizar nossa chamada do jCrop:

$('#jcrop').Jcrop({
	onChange: exibePreview,
	onSelect: exibePreview,
	aspectRatio: 1
});

Note a propriedade aspectRatio, utilizada para amarrar largura e altura do crop, mantendo a proporção.

Tudo OK no frontend. Abaixo você confere o código do formulário de envio e o script para processamento da imagem pós-envio (validação e redimensionamento para evitar arquivos gigantes no crop).

<form name="frm-jcrop" id="frm-jcrop" method="post" action="index.php" enctype="multipart/form-data">
	<p>
		<label>Envie uma imagem:</label>
		<input type="file" name="imagem" id="imagem" />
		<input type="submit" value="Enviar" />
	</p>
</form>
// processa arquivo
$imagem		= isset( $_FILES['imagem'] ) ? $_FILES['imagem'] : NULL;
$img		= '';
// verifica se arquivo foi enviado para o servidor
if( $imagem['tmp_name'] )
{
	// move arquivo para o servidor
	if( move_uploaded_file( $imagem['tmp_name'], $imagem['name'] ) )
	{
		include( 'm2brimagem.class.php' );
		$oImg = new m2brimagem( $imagem['name'] );
		if( $oImg->valida() == 'OK' )
		{
			// redimensiona imagem para evitar arquivos grandes
			$oImg->redimensiona( '400', '', '' );
			$oImg->grava( $imagem['name'] );
			// retorna dimensões da imagem e configura variáveis para o jCrop
			$imagesize 	= getimagesize( $imagem['name'] );
			$img		= '<img src="'.$imagem['name'].'" id="jcrop" '.$imagesize[3].' />';
			$preview	= '<img src="'.$imagem['name'].'" id="preview" '.$imagesize[3].' />';
		}
		else
		{
			// imagem inválida, exclui do servidor
			unlink( $imagem['name'] );
		}
	}
}

Está quase pronto. Temos um formulário para envio da imagem, e todo o javascript que vai processar o crop e atualizar o nosso preview. Falta o código PHP que vai de fato recortar a imagem enviada. Para isso utilizaremos a classe m2brimagem (leia mais sobre ela aqui).

O processamento será feito via AJAX/post, apenas para agilizar o retorno ao usuário, mas nada impede você de fazer o crop em um novo envio do formulário.

$('#btn-crop').click(function(){
	$.post( 'crop.php', {
		img:img, 
		x: $('#x').val(), 
		y: $('#y').val(), 
		w: $('#w').val(), 
		h: $('#h').val()
	}, function(){
		$('#div-jcrop').html( '<img src="'+img+'?'+Math.random()+'" width ="'+$('#w').val()+'" height ="'+$('#h').val()+'" />' );
	});
	return false;
});

Uma vez processado, nosso formulário exibirá uma nova tela, com a opção de recortar e salvar um pedaço da imagem enviada. No exemplo você pode observar que, além da imagem e da interface para crop, exibimos também duas janelas adicionais: o preview, já comentado anteriormente; e um pequeno debug, exibindo as informações e coordenadas em tempo real (tudo isso atualizado via exibePreview).

Ao clicar no botão salvar, o usuário executa, via AJAX, o script abaixo, mais uma vez utilizando a classe m2brimagem para o processamento. O script recebe como parâmetro o ponto inicial X e Y do crop, além da largura e altura do mesmo, e cria a versão recortada da imagem original. Após a execução, nosso elemento img é atualizado. Como nesse caso a imagem final possui o mesmo nome da imagem original, utilizamos o “?” com um número randômico (Math.random()) para evitar cache (essa dica é bem legal para sistemas com upload de imagens).

if( $_SERVER['REQUEST_METHOD'] == 'POST' )
{
	include( 'm2brimagem.class.php' );
	$oImg = new m2brimagem( $_POST['img'] );
	if( $oImg->valida() == 'OK' )
	{
		$oImg->posicaoCrop( $_POST['x'], $_POST['y'] );
		$oImg->redimensiona( $_POST['w'], $_POST['h'], 'crop' );
		$oImg->grava( $_POST['img'] );
	}
}
exit;

Outras opções

Talvez tudo pareça um pouco confuso olhando os códigos assim de forma separada. Baixe os exemplos que você vai entender melhor. No site do plugin você encontra a documentação completa, em inglês, além de outros exemplos. Uma opção que utilizo bastante é especificar largura e altura fixas. Quando o usuário faz o upload de um avatar, por exemplo, vale a pena limitar o tamanho no jCrop (ou então fazer alguma coisa proporcional). As propriedades minSize e maxSize delimitam a área mínima e máxima do crop.

$('#jcrop').Jcrop({
	onChange: exibePreview,
	onSelect: exibePreview,
	minSize		: [ 200, 200 ], 
	maxSize		: [ 200, 200 ],
	allowResize	: false,
	addClass	: 'custom'
});

No exemplo acima o crop vai ter sempre 200×200 pixels de dimensão. Além disso configuramos a propriedade allowResize com false, para evitar o redimensionamento da seleção. Outra propriedade legal é a addClass, para definir um estilo personalizado na seleção. No exemplo abaixo a linha fica com uma borda sólida ao invés do pontilhado.

.custom .jcrop-vline, .custom .jcrop-hline {
	background: #FF3366;
}

Esse foi o jCrop, um bônus e tanto para a interface dos seus sistemas. Espero que tenham gostado da leitura e qualquer dúvida utilizem a área de comentários abaixo. Até a próxima!

 

Fonte: www.daviferreira.com