colorear parte de una imagen con canvas

Colorear parte de imagen con Canvas

Hace unos días publiqué una entrada en la que explicaba cómo hacer uso de la propiedad CSS mix-blend-mode para conseguir colorear parte de una imagen. Pero como suele ocurrir frecuentemente, los navegadores de Microsoft se han quedado atrás en la adopción de esa propiedad a pesar de ser una recomendación oficial, así que la solución planteada no funcionaba correctamente en ellos. En esta ocasión ampliaremos lo explicado anteriormente para ofrecer una solución que cubre todos los navegadores recientes.

Como ya teníamos una solución válida para la mayoría de los navegadores, lo primero que haremos es detectar si el navegador soporta los efectos de fusión de capas por CSS, para ofrecerle una solución alternativa en caso contrario. Y como siempre que queremos detectar si el navegador soporta o no determinada propiedad, lo mejor es echar mano de Modernizr.

modernizr

Para construir nuestro script de detección de propiedades, seleccionamos “CSS Background Blend Mode” y pulsamos el botón “Build”. Eso es todo, no necesitamos detectar nada más. En nuestro caso lo hemos renombrado como modernizr-backgroundblend.js

<script type="text/javascript" src="js/modernizr-backgroundblend.js"></script>

<script type="text/javascript">

if (Modernizr.backgroundblendmode) {
  	// mix-blend-mode supported
} else {
  	// mix-blend-mode not-supported (use canvas)
}
</script>
La página

La idea es seguir conservando la versión anterior en aquellos navegadores en los que funciona, y ofrecer una alternativa a través de canvas para los demás. Es cierto que la opción basada en canvas funcionaría correctamente en todos los navegadores recientes, pero mantendremos también la versión anterior por razones de rendimiento.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8" >

	<title>Change Image Color</title>

	<link rel="stylesheet" href="styles/styles.css">
	<link rel="stylesheet" href="styles/jquery.simplecolorpicker.css">

	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
	<script type="text/javascript" src="js/jquery.simplecolorpicker.js"></script>
    <script type="text/javascript" src="js/modernizr-backgroundblend.js"></script>

</head>

<body>

<div id="tshirt_container">
	<img src="images/white-t-shirt.jpg" class="tshirt_layer" id="tshirt_layer_1">
	<img src="images/guy.png" class="tshirt_layer" id="tshirt_layer_2">	
</div>

<select name="colorpicker-shortlist" id="colorpicker">
  <option value="#7bd148"">Green</option>
  <option value="#5484ed">Bold blue</option>
  <option value="#a4bdfc">Blue</option>
  <option value="#46d6db">Turquoise</option>
  <option value="#7ae7bf">Light green</option>
  <option value="#51b749">Bold green</option>
  <option value="#fbd75b">Yellow</option>
  <option value="#ffb878">Orange</option>
  <option value="#ff887c">Red</option>
  <option value="#dc2127">Bold red</option>
  <option value="#dbadff">Purple</option>
  <option value="#e1e1e1">Gray</option>
  <option value="#cabdbf">#cabdbf</option>
</select>

<script type="text/javascript">
$(window).load(function() {//we need to be sure that the images have loaded 

	if (Modernizr.backgroundblendmode) {// mix-blend-mode supported

    	$('select[name="colorpicker-shortlist"]').simplecolorpicker('selectColor', '#7bd148');
   	 	$('#tshirt_layer_2').css('background-color', $('select[name="colorpicker-shortlist"]').val());
		$('select[name="colorpicker-shortlist"]').on('change', function() {
    		$('#tshirt_layer_2').css('background-color', $('select[name="colorpicker-shortlist"]').val());
  		});	

	} else {// mix-blend-mode not-supported (use canvas)

        function hexToRgb(hex) {       
            var color_array = new Object();
            hex = hex.replace(/[^0-9A-F]/gi, '');
    		var bigint = parseInt(hex, 16);
    		color_array[0] = (bigint >> 16) & 255;
    		color_array[1] = (bigint >> 8) & 255;
    		color_array[2] = bigint & 255;
    		return color_array;
		}

        $('select[name="colorpicker-shortlist"]').simplecolorpicker('selectColor', '#7bd148');

        $('select[name="colorpicker-shortlist"]').on('change', function() {

    		multiplyColor = hexToRgb($('select[name="colorpicker-shortlist"]').val());

    		redraw();

  		});

  		function redraw(){

  			imageBottom = document.getElementById('tshirt_layer_1');
  			$('#tshirt_container canvas').remove(); 
			canvas = document.createElement('canvas');
			canvas.width = imageBottom.width;
			canvas.height = imageBottom.height;
			imageBottom.parentNode.insertBefore(canvas, imageBottom);

			ctx = canvas.getContext('2d');
			ctx.drawImage(imageBottom, 0, 0);

			// Get the CanvasPixelArray from the given coordinates.
			imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
			pix = imgData.data;

			//multiplyColor = [80, 120, 10];
			multiplyColor = hexToRgb($('select[name="colorpicker-shortlist"]').val());

	    	// Loop over each pixel and change the color.
			for (var i = 0, n = pix.length; i < n; i += 4) {
    			pix[i  ] = multiply(multiplyColor[0], pix[i  ]); // red
    			pix[i+1] = multiply(multiplyColor[1], pix[i+1]); // green
    			pix[i+2] = multiply(multiplyColor[2], pix[i+2]); // blue
    			// pix[i+3] is alpha channel (ignored)
			}
			// Draw the result on the canvas
			ctx.putImageData(imgData, 0, 0);

			function multiply(topValue, bottomValue){
  				return topValue * bottomValue / 255;
			}

		}

		redraw();

	}

})
</script>
</body>
</html>

Si lo comparas con la solución planteada en la entrada anterior verás que la parte

if (Modernizr.backgroundblendmode) {// mix-blend-mode supported

    	$('select[name="colorpicker-shortlist"]').simplecolorpicker('selectColor', '#7bd148');
   	 	$('#tshirt_layer_2').css('background-color', $('select[name="colorpicker-shortlist"]').val());
		$('select[name="colorpicker-shortlist"]').on('change', function() {
    		$('#tshirt_layer_2').css('background-color', $('select[name="colorpicker-shortlist"]').val());
  		});	

}

es esencialmente idéntica y que todo lo nuevo ha sido añadido si no se cumple la condición (else).
También verás que hemos cambiado $(document).ready(function() { por $(window).load(function() {
Esto es así porque necesitamos asegurarnos de que las imágenes han sido cargadas para poder hacer operaciones con ellas.

La programación para crear un efecto multiply aplicando un color a una imagen se basa directamente en lo explicado en esta página añadiéndole algunas variaciones. En primer lugar lo hemos “empaquetado” en una función (redraw), y le hemos añadido una nueva función (hexToRgb) para convertir los valores devueltos por el selector de colores al formato de array [r,g,b] que necesita.

Como necesitamos poder aplicar la función múltiples veces, dentro de la misma usamos la expresión jQuery $('#tshirt_container canvas').remove(); para eliminar el elemento canvas antes de volverlo a recrear.

else {// mix-blend-mode not-supported (use canvas)

        function hexToRgb(hex) {       
            var color_array = new Object();
            hex = hex.replace(/[^0-9A-F]/gi, '');
    		var bigint = parseInt(hex, 16);
    		color_array[0] = (bigint >> 16) & 255;
    		color_array[1] = (bigint >> 8) & 255;
    		color_array[2] = bigint & 255;
    		return color_array;
		}

        $('select[name="colorpicker-shortlist"]').simplecolorpicker('selectColor', '#7bd148');

        $('select[name="colorpicker-shortlist"]').on('change', function() {

    		multiplyColor = hexToRgb($('select[name="colorpicker-shortlist"]').val());

    		redraw();

  		});

  		function redraw(){

  			imageBottom = document.getElementById('tshirt_layer_1');
  			$('#tshirt_container canvas').remove(); 
			canvas = document.createElement('canvas');
			canvas.width = imageBottom.width;
			canvas.height = imageBottom.height;
			imageBottom.parentNode.insertBefore(canvas, imageBottom);

			ctx = canvas.getContext('2d');
			ctx.drawImage(imageBottom, 0, 0);

			// Get the CanvasPixelArray from the given coordinates.
			imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
			pix = imgData.data;

			//multiplyColor = [80, 120, 10];
			multiplyColor = hexToRgb($('select[name="colorpicker-shortlist"]').val());

	    	// Loop over each pixel and change the color.
			for (var i = 0, n = pix.length; i < n; i += 4) {
    			pix[i  ] = multiply(multiplyColor[0], pix[i  ]); // red
    			pix[i+1] = multiply(multiplyColor[1], pix[i+1]); // green
    			pix[i+2] = multiply(multiplyColor[2], pix[i+2]); // blue
    			// pix[i+3] is alpha channel (ignored)
			}
			// Draw the result on the canvas
			ctx.putImageData(imgData, 0, 0);

			function multiply(topValue, bottomValue){
  				return topValue * bottomValue / 255;
			}

		}

		redraw();

	}

El CSS

Modernizr también introduce las clases  .backgroundblendmode y .no-backgroundblendmode para poder realizar selecciones en nuestro CSS basados en la detección de esta propiedad. Nuestro CSS queda prácticamente idéntico al del ejemplo anterior, pero aprovechamos para añadir la propiedad CSS mix-blen-mode: multiply solo en el caso de estar soportada.

.simplecolorpicker{
 max-width: 300px;
}
.simplecolorpicker span.color[data-selected]:after {
  content: '\2714'; /* Ok/check mark */
  width: 12px;
  display: block;
  padding-left: 4px
}

/*t-shirt*/
#tshirt_container{
 position: relative;
 width: 300px;
 height: 450px;
}

#tshirt_container .tshirt_layer, #tshirt_container canvas{
 position: absolute;
 display: table;
  width: 300px;
 height: 450px;
}

#tshirt_layer_1{
 z-index: 1;
}

#tshirt_layer_2{
	z-index: 3;
}
.backgroundblendmode #tshirt_layer_2 { 
	-o-mix-blend-mode: multiply;
	-ms-mix-blend-mode: multiply;
	-moz-mix-blend-mode: multiply;
	mix-blend-mode: multiply;
	-webkit-mix-blend-mode: multiply;
}

.no-backgroundblendmode  #tshirt_layer_2  { 
/*styles if background blend mode is not supported*/ 
}

#tshirt_container canvas{
 	z-index: 2;
}
El resultado

Aquí tienes el resultado de todo el código anterior. Puedes usar el botón “Descargar” al final de la página para bajarte una copia con todos los scripts, estilos y archivos necesarios.

t-shirt backgroundguy with t-shirt

Paraguas
Cambiar el color de parte de una imagen