Saltar al contenido

clip: el recorte y su interacción con la pila

Usar la región de recorte del canvas, entender por qué solo se puede reducir y nunca ampliar, y combinarla con la composición de forma controlada.

La operación

JS
ctx.beginPath();ctx.arc(150, 150, 100, 0, Math.PI * 2);ctx.clip();                     // regla nonzero por defectoctx.clip('evenodd');            // o la regla par-imparctx.clip(rutaPath2D);           // o una Path2D directamente

A partir de la llamada, todo lo que dibujes se recorta a esa región. El recorte respeta el antialiasing: los bordes de la región no son duros, sino que tienen cobertura parcial, lo que da recortes suaves.

clip no consume la ruta: después de recortar, la ruta actual sigue ahí y puedes rellenarla o trazarla.

Y clip usa la matriz de transformación vigente en el momento de construir la ruta, no en el de aplicar el recorte, por la misma razón que el resto de las rutas: los puntos se transforman al añadirlos.

Solo se reduce

Llamar a clip dos veces da la intersección de ambas regiones, nunca la unión.

JS
ctx.beginPath(); ctx.rect(0, 0, 200, 200); ctx.clip();ctx.beginPath(); ctx.rect(100, 100, 200, 200); ctx.clip();// La region efectiva es el cuadrado de (100,100) a (200,200)

No hay ningún método para ampliar la región ni para quitarla. Las dos únicas formas de volver atrás son:

restore() desde un save() anterior al recorte. Es la vía normal.

reset(), que restablece todo el contexto y además borra el búfer. Es la vía de emergencia.

De ahí sale la regla que no admite excepciones: todo clip va precedido de un save.

JS
ctx.save();ctx.beginPath();ctx.roundRect(20, 20, 200, 140, 16);ctx.clip();ctx.drawImage(foto, 0, 0, 240, 180);ctx.restore();                    // sin esto, el recorte es para siempre

Olvidar ese save produce un canvas que deja de pintar la mayor parte de las cosas, y como el síntoma aparece lejos de la causa, es de los errores que más tiempo consumen.

Recortar a la unión de varias formas

Como clip solo intersecta, recortar a la unión de dos formas exige construirlas en una sola ruta antes de recortar:

JS
ctx.save();ctx.beginPath();ctx.arc(120, 120, 60, 0, Math.PI * 2);ctx.moveTo(260, 120);ctx.arc(200, 120, 60, 0, Math.PI * 2);     // segunda subruta en la MISMA rutactx.rect(80, 200, 160, 60);                 // terceractx.clip();                                  // recorta a la union de las tresdibujarFondo(ctx);ctx.restore();

Con la regla nonzero, las subrutas del mismo sentido se unen; las de sentido contrario producirían agujeros. Con evenodd, las zonas de solape se convierten en agujeros, que casi nunca es lo que se quiere para una unión.

Recorte y composición

Aquí está la interacción que resuelve el mayor problema de los operadores destructivos. Un recorte activo limita el alcance de la composición. Un destination-in con clip activo solo afecta a la región recortada; fuera de ella, el canvas queda intacto.

JS
ctx.save();ctx.beginPath();ctx.rect(40, 40, 240, 180);ctx.clip();                       // confina todo lo que siguectx.drawImage(foto, 40, 40, 240, 180);ctx.globalCompositeOperation = 'destination-in';const g = ctx.createLinearGradient(0, 40, 0, 220);g.addColorStop(0, 'rgba(0,0,0,1)');g.addColorStop(1, 'rgba(0,0,0,0)');ctx.fillStyle = g;ctx.fillRect(40, 40, 240, 180);ctx.restore();                     // recupera el operador Y el recorte

Ese patrón evita el canvas auxiliar y es más barato. Su límite es que el recorte tiene bordes duros de forma efectiva para la composición: si el destino tenía contenido justo fuera del rectángulo de recorte, ese contenido no participa y puede haber un corte visible. Cuando el efecto tiene que ser suave en los bordes, la capa auxiliar sigue siendo lo correcto.

Y clearRect también respeta el recorte, lo que hace que la rutina de limpieza de un canvas tenga que quitar el recorte antes:

JS
function limpiarTodo(ctx) {  ctx.save();  ctx.setTransform(1, 0, 0, 1, 0, 0);  // Si hay un recorte activo, este clearRect no lo salta.  // La unica garantia es que el save/restore de fuera lo haya quitado.  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);  ctx.restore();}

clip frente a las alternativas

Hay tres formas de conseguir un recorte y cada una tiene su sitio.

clip es la más directa. Su alcance está bien delimitado por la pila, es componible y no necesita canvas auxiliares. Su pega es el coste con formas no rectangulares y que no admite máscaras con transparencia parcial: una región está dentro o fuera, aunque el borde tenga antialiasing.

destination-in en una capa admite máscaras con degradado, lo que permite desvanecidos y transparencias graduales. Su pega es que necesita una capa y que afecta a todo el canvas si no la usas.

Recortar en el origen con drawImage es la más barata cuando la forma es un rectángulo y lo que recortas es una imagen: la firma de nueve parámetros ya hace el recorte sin ninguna máscara.

Necesidad Herramienta
Limitar el área de dibujo a un rectángulo clip con rect, o nada si puedes acotar las coordenadas
Foto dentro de una forma con borde duro clip con la ruta
Foto que se desvanece hacia un borde destination-in con degradado, en capa
Un trozo de una imagen drawImage con nueve parámetros
Muchos elementos con la misma forma recortada Canvas de caché y drawImage