martes, 29 de octubre de 2013

Rotar una imagen

A partir de lo que hemos aprendido en los dos posts anteriores: Transformaciones en el canvas y Guardar y restaurar el contexto del canvas vamos a escribir una función con la que rotar fácilmente una imagen.

  • drawImageR(context, image, x, y, angle)
La hemos llamado drawImageR para diferenciarla de drawImage y para dar una pista de que sirve para rotar imágenes. Los argumentos son el contexto del canvas en el que dibujar, la imagen, las coordenadas x, y en las cuales dibujar la imagen y el angulo de rotación en radianes (respecto al centro de la imagen).
La forma de usarla difiere de la función drawImage porque drawImage es un método del contexto del canvas, mientras que drawImageR no, por lo tanto, sea ctx el contexto del canvas, haremos:
ctx.drawImage(...)
o
drawImageR(ctx, ...)

La función se puede modificar y ampliar para cubrir otras necesidades específicas, como por ejemplo rotar la imagen respecto a otro punto distinto del centro o dibujar un trozo específico de la imagen, como permiten las versiones con más parametros de drawImage.

La definición de drawImageR es la siguiente:
  function drawImageR(context, image, x, y, angle){
 var w_half = image.width/2;
 var h_half = image.height/2;

 context.save();
 context.translate(x + w_half, y + h_half);
 context.rotate(angle);
 context.drawImage(image, -w_half, -h_half);
 context.restore(); 
  }

Ejemplo

Un ejemplo sencillo, suponiendo que en la carpeta /images tenemos la imagen 1.jpg (un cuadrado con la mitad izquierda roja y la mitad derecha verde) usaremos la función ya conocida loadImages para cargarla y después dibujaremos una copia rotada y otra sin rotar, en la misma posición.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
 <canvas id="my_canvas" width="300px" height="200px"
 style="border:1px solid #000000;"></canvas> 
 <script type="text/javascript">
  var canvas = document.getElementById('my_canvas');
  var ctx = canvas.getContext('2d');
  var images = loadImages('./images/1.jpg', load_done);
  var cuadrado = images[0];
      
  function drawImageR(context, image, x, y, angle){
 var w_half = image.width/2;
 var h_half = image.height/2;

 context.save();
 context.translate(x + w_half, y + h_half);
 context.rotate(angle);
 context.drawImage(image, -w_half, -h_half);
 context.restore(); 
  }
  
  function loadImages() { //images and callback
 var n = arguments.length - 1;
 var callback = arguments[n]
 var images = new Array();
 var count = 0;
 
 for (i = 0; i < n; i++) {
  images[i] = new Image();
  images[i].onload = function(){
   count++;
   if (count == n){
    callback ();
   };
  };
  images[i].src = loadImages.arguments[i];
 };
 return images;  
}

 function load_done() {
  drawImageR(ctx, cuadrado, 20, 50, 45*Math.PI/180);
  ctx.drawImage(cuadrado, 20, 50);
 }
 
 </script> 
</body>
</html>

martes, 1 de octubre de 2013

Guardar y restaurar el contexto del canvas

Esta entrada continua la entrada anterior: Transformaciones en el canvas.
El contexto del canvas tiene una pila de estados. El estado del contexto del canvas contiene los valores actuales de distintos parámetros que se han fijado para el canvas:
  • Las transformaciones: traslación, rotación, escalado
  • La región de dibujo (clipping region)
  • Los valores actuales de los atributos: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline. 
¿Cómo recuperamos el estado del canvas después de haber hecho una serie de modificaciones? Para ello el estado del contexto se puede almacenar en una pila LIFO (Last In First Out). Lo que haremos para recuperar el estado que teníamos antes de hacer modificaciones será lo siguiente:
  1. guardar el estado
  2. hacer las modificaciones
  3. dibujar lo que queramos con los nuevos ajustes
  4. recuperar el estado
Para ello el contexto del canvas dispone de dos funciones:
  • context.save(): guarda el estado actual en la parte superior de la pila
  • context.restore(): saca (y aplica al contexto) el estado de la parte superior de la pila

Ejemplo

<!DOCTYPE html>
<html>
<head>
</head>
<body>
 <canvas id="my_canvas" width="300px" height="200px"
 style="border:1px solid #000000;"></canvas> 
 <script type="text/javascript">
  var canvas = document.getElementById('my_canvas');
  var ctx = canvas.getContext('2d');
      
  var x = 10;
  var y = 10;
  var w = 50;
  var h = 50;
  

  // estado original: 0
  ctx.fillRect(x, y, w, h);
  ctx.save();
 
  // estado 1 
  ctx.fillStyle = "#ff0000"; // rojo
  ctx.fillRect(x+w, y, w, h);
  ctx.save();
  
  // estado 2
  ctx.fillStyle = "#00ff00"; // verde
  ctx.fillRect(x+2*w, y, w, h);
  
  // recuperamos el estado 1
  ctx.restore();
  ctx.fillRect(x+3*w, y, w, h);
  
  // recuperamos el estado 0
  ctx.restore();
  ctx.fillRect(x+4*w, y, w, h);
 
 </script> 
</body>
</html>
 


En este ejemplo únicamente hemos modificado el color de relleno, pero el mismo principio se aplica a cualquiera de los parametros contenidos en el contexto.

Esta entrada continua la entrada anterior: Transformaciones en el canvas.

miércoles, 28 de agosto de 2013

Transformaciones en el canvas

Este post tiene una continuación en la entrada: Guardar y restaurar el contexto del canvas.
Las transformaciones básicas en el  canvas son 3: traslación, rotación y escalado.
La forma en que funcionan es poco intuitiva puesto que lo que hacen no es trasladar, rotar y escalar elementos en el canvas sino que actúan sobre el origen y los ejes de coordenadas del canvas, modificando su posición, orientación y escala, de forma que los elementos dibujados antes de una transformación permanecen inalterados y los que se dibujen después de aplicar la transformación son afectados por ella.
Las transformaciones se aplican sobre el contexto del canvas.

Traslación

context.translate(x,y)
Traslada el origen de cordenadas del canvas desde la esquina superior izquierda al punto x,y.

Rotación

context.rotate(radianes)
Rota los ejes de coordenadas en torno al origen, un angulo especificado en radianes, en sentido horario. Para pasar de grados a radianes:
angulo_en_radianes = angulo_en_grados * (Math.PI/180)

Escalado

context.scale(factorX, factorY)
Mutiplica las coordenadas en el eje X e Y por el factorX y el factorY.


Ejemplo

<!DOCTYPE html>
<html>
<head>
</head>
<body>
 <canvas id="my_canvas" width="300px" height="200px"
 style="border:1px solid #000000;"></canvas> 
 <script type="text/javascript">
  var canvas = document.getElementById('my_canvas');
  var ctx = canvas.getContext('2d');
      
  var x = 10;
  var y = 10;
  var w = 50;
  var h = 50;
  var factorX = 0.5;
  var factorY = 0.5;
  
  // escalado
  ctx.scale(factorX, factorY);
  
  ctx.fillStyle = "#ff0000"; // rojo
  ctx.fillRect(x, y, w, h);
  
  // traslación
  ctx.translate(100, 50);
  
  ctx.fillStyle = "#00ff00"; // verde
  ctx.fillRect(x, y, w, h);
  
  // rotación
  ctx.rotate(30 * Math.PI/180);
  
  // dibujamos el nuevo eje x
  ctx.beginPath();
  ctx.moveTo(0,0);
  ctx.lineTo(50,0);
  ctx.stroke();
  
  // y el nuevo eje Y
  ctx.beginPath();
  ctx.moveTo(0,0);
  ctx.lineTo(0,50);
  ctx.stroke();
  
  ctx.fillStyle = "#0000ff"; // azul
  ctx.fillRect(x, y, w, h);
 
 </script> 
</body>
</html>
En el ejemplo se puede probar a cambiar la escala, cambiando factorX y factorY. El ejemplo tiene factorX = 0.5 y factorY = 0.5 (mitad de escala). El tamaño normal se consigue con factorX = 1, factorY = 1 y el doble de tamaño se consigue con factorX = 2, factorY = 2.


Este post tiene una continuación en la entrada: Guardar y restaurar el contexto del canvas 

lunes, 5 de agosto de 2013

Dibujar imágenes en el canvas con drawImage

Podemos dibujar en el canvas el contenido de un archivo de imagen. Los 2 formatos de imagen más habituales en la actualidad son:
  • jpg: formato comprimido con pérdida de calidad y sin transparencia.
  • png: formato comprimido sin pérdida de calidad y con posibilidad de transparencia.
Tal vez en un futuro ambos queden relegados por un nuevo formato libre: webp, pero de momento no hay indicios de tal cosa.
En general usaremos el formato jpg por ser de tamaño más reducido, a no ser que no queramos que la imagen pierda calidad o queramos usar una imagen con transparencia, en cuyo caso usaremos el formato png.

Para dibujar una imagen en el canvas usaremos la función drawImage del contexto del canvas (context.drawImage()), que tiene varias posibilidades:
  • context.drawImage(img, x, y): dibuja la imagen entera y sin escalar en el canvas, poniendo su esquina superior izquierda en el punto x,y del canvas.
  • context.drawImage(img, x, y, ancho, alto): posiciona la imagen en el punto x,y del canvas y la reescala para que tenga la anchura y altura especificadas.
  • context.drawImage(img, ox, oy, oancho, oalto, x, y, ancho, alto): coge un trozo de la imagen a partir del punto ox,oy, de anchura oancho y altura oalto y lo coloca en el canvas en el punto x, y, reescalándolo para que tenga la anchura ancho y altura alto.

Ejemplo

usaremos la siguiente imagen, que se puede guardar con click derecho del ratón sobre ella y "Guardar imagen". La guardaremos como planeta.jpg.


En el mismo directorio en que tengamos la imagen creamos un archivo html con el siguiente contenido.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <canvas id="my_canvas" width="300px" height="200px"
           style="border:1px solid #000000;"></canvas> 
    <script type="text/javascript">
 var canvas = document.getElementById('my_canvas');
 var ctx = canvas.getContext('2d');
 var imagen = new Image();
 imagen.onload = pinta;
 imagen.src = 'planeta.jpg';

 function pinta(){     
            // pintamos la imagen en la esquina superior izquierda del
            // canvas, con el tamaño original.
     ctx.drawImage(imagen, 0, 0);
     // pintamos la imagen en en el punto 110, 0, reescalada.
     ctx.drawImage(imagen, 110, 0, 1.5*imagen.width, 
            0.5*imagen.height);
     // pintamos un trozo de imagen, el cuadrante inferior
            // derecho, en el punto 0, 110, sin reescalar.
     ctx.drawImage(imagen, 50, 50, 50, 50, 0, 110, 50, 50);
     // reescalado (doble altura)
     ctx.drawImage(imagen, 50, 50, 50, 50, 110, 110, 50, 2*50);
 }
 </script> 
</body>
</html>
Como nota interesante del ejemplo, ver que el objeto de tipo Image tiene un par de campos width y height desde los que podemos recuperar la anchura y altura de la imagen.




lunes, 22 de julio de 2013

Cargador de imágenes

En un post anterior ya vimos como cargar una imagen. El proceso incluía crear un objeto imagen para cada imagen a cargar, definir una función a ejecutar cuando termise la carga y por último, especificar la localización de la imagen. Es un proceso laborioso que puede hacerse un poco pesado. A continuación presentamos un sencillo cargador de imágenes que nos permitirá especificar todas las ubicaciones de las imágenes a cargar a la vez y una única función de callback para cuando terminen de cargarse todas ellas.
Conviene guardar el código siguiente en un archivo aparte, por ejemplo "loadImages.js" para poder incluirlo fácilmente en aquellos proyectos en los que necesitemos cargar varias imágenes de forma sencilla.
// loadImages.js
// arguments:
//  string urls with location of images to load
//  callback function
// returns:
//  array of Image objects

function loadImages() { //images and callback
 var n = arguments.length - 1; // number of images to load
 var callback = arguments[n]
 var images = new Array();
 var count = 0;
 
 for (i = 0; i < n; i++) {
  images[i] = new Image();
  images[i].onload = function(){
   count++;
   if (count == n){
    callback ();
   };
  };
  images[i].src = loadImages.arguments[i];
 };
 return images;  
}

Uso

La función loadImages devuelve un array que contiene las imágenes cargadas. Acepta un número variable de argumentos. Los argumentos serán cadenas de texto especificando la ubicación de las imagenes a cargar, excepto el último argumento, que será la función de callback, que debe especificarse sin comillas y sin los paréntesis de la llamada, como en el siguiente ejemplo:
<!DOCTYPE html>
<html>

    <head>
    <script type="text/javascript" src="./loadImages.js"></script>
    <script type='text/javascript'> 

 function load_done() {
            text = "Images loaded:<br>";
         for(var i=0; i< images.length; i++)
  {
      text += images[i].src + "<br>";
  }
  document.write(text);
 }
   
 var images = loadImages('./images/im1.jpg',
    './images/im2.jpg',
    './images/im3.jpg',
    './images/im4.jpg',
    './images/im5.jpg',
    './images/im6.jpg',
    './images/im7.jpg',
    './images/im8.jpg',
    './images/im9.jpg',
    load_done);
    </script>
    </head>

    <body>
    </body>

</html>
En este ejemplo hemos especificado las ubicaciones relativas de las imágenes a cargar, pero también se podría haber usado caminos absolutos e incluso urls como
'http:\\www.lapaginaquesea.com\imagencualquiera.jpg

miércoles, 10 de julio de 2013

Carga de imágenes

El proceso para cargar una imagen que podamos usar desde javascript consta de tres pasos:

  1. Crear un objeto Image.

var imagen = new Image();

  1. Especificar la función de callback

Es la función que se ejecuta cuando termine la carga de la imagen.
imagen.onload = callback; 
Importante! la función de callback se especifica sin encerrarla en comillas y sin poner los paréntesis.

  1. Especificar la imagen

Puede especificarse mediante un camino relativo al archivo de imagen o mediante una url. Una vez definida la fuente desde la que se debe cargar la imagen, la carga comienza inmediatamente, por lo que hay que si se quiere especificar una función de callback, debe especificarse antes de especificar la fuente de la imagen.
imagen.src="miImagen.jpg;"

Ejemplo

<!DOCTYPE html>
<html>
<head>
</head>
<body>
 <canvas height="200px" id="my_canvas" style="border: 1px
                 solid #000000;" width="300px"></canvas> 
 <script type="text/javascript">
  var canvas = document.getElementById('my_canvas');
  var ctx = canvas.getContext('2d');
  var imagen = new Image();
  imagen.onload = pinta;
  imagen.src = "http://1.bp.blogspot.com/-t9kNjyxhS_k/" +
       "UXRLc-tCXuI/AAAAAAAAAEQ/Gmn3RR06Org/s1600/canvas1.png";

  function pinta(){                
   ctx.drawImage(imagen, 0, 0);
  }
 </script> 
</body>
</html>

viernes, 17 de mayo de 2013

Medir el tiempo de ejecución

Podemos usar la consola de las herramientas para desarrolladores para ver el tiempo que tarda en ejecutarse una parte del código y comprobar su rendimiento. En Google Chrome esto se hace con 2 instrucciones:

  • console.time(nombre del timer): se coloca antes del código que queremos medir. Inicia un temporizador con el nombre que hayamos elegido, que será una cadena de texto.
  • console.timeEnd(nombre del timer): se coloca después del código que queremos medir. Termina el temporizador con el nombre asociado, que será uno de los que hayamos iniciado antes con console.time(). Además escribe a la consola el resultado.

Uso

console.time('nombre');
// código a cronometrar

console.timeEnd('nombre');

Ejemplo

Por ejemplo, lo podemos usar para medir el tiempo que tarda en pintar 10000 lineas aleatorias de colores al azar, como vimos en un post anterior:
<!DOCTYPE html>
<html>
 <head>
    <script type="text/javascript" >
        var canvas;
        var ctx; //contexto del canvas.
        
        function setup(){
            // accedemos al canvas
            canvas = document.getElementById('my_canvas');

            // si se puede acceder al contexto del canvas
            if (canvas.getContext){ 
                // accedemos al contexto del canvas 
                // y llamamos a nuestra función de dibujo.
                ctx = canvas.getContext('2d');
                draw();
            }
            else { // el canvas no está soportado
                alert('Este navegador no es compatible con canvas');
            }
        }
        
        // nuestra función de dibujo
        function draw(){                
            // lo ajustamos al tamaño de la ventana del navegador
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            // dibujamos. En este caso, 10000 lineas aleatorias
            // y medimos lo que tarda
            console.time('tiempo que tarda en pintar');
            
            pintaLineasAleatorias(canvas.width, canvas.height, 10000);
            
            // hasta que termina
            console.timeEnd('tiempo que tarda en pintar');
        }
        
        function pintaLineasAleatorias(width, height, n)
        {
            var x1, y1, x2, y2;
   
            for (var i=0; i< n; i++)
     {
         x1 = Math.floor((Math.random()*(width + 1)));
         x2 = Math.floor((Math.random()*(width + 1)));
         y1 = Math.floor((Math.random()*(height +1)));
  y2 = Math.floor((Math.random()*(height +1)));
  ctx.strokeStyle = 'rgb('
                    + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ')';
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  ctx.lineTo(x2,y2);
                ctx.closePath();
  ctx.stroke();
      }
        }
    </script>
 </head>
 <body style="margin:0; padding:0; width:100%; height:100%;" onload="setup()" >
  <canvas id="my_canvas" style="display:block;"></canvas>
 </body>
</html>
Si abrimos el archivo html anterior en el Google Chrome y luego abrimos la consola de javascript (Ctrl+Mayús+J) veremos el tiempo que ha tardado en dibujar las 10000 lineas.