Contenido
Generar imágenes con Deno de la forma más eficiente con ImageScript
26/3/2023 • 30/60 minutos de lectura
Recientemente estuve en búsqueda de una librería con la que poder generar
imágenes de forma automatizada para desplegar una API con Deno. Encontré varias
opciones y después de probarlas, terminé eligiendo
ImageScript.
ImageScript es una librería súper liviana ya que no tiene
ninguna dependencia y utiliza métodos de Web Assembly para codificar y
descodificar los binarios de las imágenes. El resultado de esto es una API que
además de ser amigable y entendible, es muy rápida y eficiente. Ideal
para cualquier tarea en la que necesitemos automatizar el proceso de crear
imágenes.
A continuación, una mini guía por varios de los métodos que están
disponibles, junto con un ejemplo final aplicando algunos de estos en una API
con Deno Fresh 💛.
Cómo utilizar ImageScript
Crear un lienzo
Para inicializar nuestra imagen, debemos importar la utilidad Image
desde la
dependencia, e instanciarla. Creemos el archivo template.ts
como plantilla:
// template.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
// Establecemos las medidas de nuestro lienzo
const HEIGHT = 800
const WIDTH = 900
// Instanciamos nuestra imagen con las medidas
const template = new Image(WIDTH, HEIGHT)
// Creamos una función con el método clone()
export default function createImage() {
return template.clone()
}
Como se puede ver arriba, luego de crear la imagen con las medidas, exportamos
una función que retorna template.clone()
. Este método crea una copia de esa
instancia para poder hacer manipulaciones a la imagen sin alterar la plantilla
inicial. La vamos a utilizar para ver varios ejemplos.
📄 Referencia: Image#clone
Dar un color de fondo
Para dar un color al fondo de nuestro lienzo, podemos usar el método fill()
// tutorial/1-fillColor.ts
import createImage from './template.ts'
// Clonamos la imagen
const image = createImage()
// Rellenamos el fondo con un color rojo #FF0000 con 100% opacidad (FF)
image.fill(0xFF0000FF)
// Guardamos el resultado en un nuevo archivo llamando al método encode()
await Deno.writeFile(`./tutorial/output/colorFilled.png`, await image.encode())
Ver código de este ejemplo
Resultado:
El método fill
recibe un valor hexadecimal indicando el color de relleno.
Podemos incluir también la opacidad del relleno.
📄 Referencia: Image#fill
Cargar o renderizar una imagen
Si tenemos una el archivo de una imagen que queremos renderizar en nuestro
lienzo, podemos utilizar el método decode
para poder transformarlo y
utilizarlo:
// tutorial/2-renderImageFS.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
// Primero leemos la imagen en nuestra carpeta
const imageFile = await Deno.readFile('./tutorial/lake.jpg')
// Luego lo convertimos en un objeto Image con el método decode
const decodedImage = await Image.decode(imageFile)
// Lo renderizamos en nuestro lienzo
image.composite(decodedImage, 0, 0)
// Guardamos la imagen nuevamente
await Deno.writeFile(
`./tutorial/output/renderedImage.png`,
await image.encode(),
)
Ver código de este ejemplo
Resultado:
Utilizando el método composite
podemos renderizar imágenes en nuestro
lienzo. El método recibe un parámetro obligatorio y 2 opcionales:
- El primer parámetro es una instancia de Image (la imagen que decodificamos con
decode()
) - El segundo y tercer párametro son las coordenadas X e Y respectivamente.
📄 Referencia: Image#composite
Renderizar imagen con una URL
¿Pero qué sucede si la imagen está en internet y sólo tengo su URL? Para ello:
// tutorial/3-renderImageFromURL.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const URL = 'https://upload.wikimedia.org/wikipedia/commons/0/05/Cat.png'
// Hacemos fetch de nuestra URL
const response = await fetch(URL)
// Creamos un objeto Uint8Array a partir del array binario de la respuesta
const imageFromURL = new Uint8Array(await response.arrayBuffer())
// Lo codificamos y colocamos en nuestro lienzo
image.composite(await Image.decode(imageFromURL), 0, 0)
await Deno.writeFile(`./tutorial/output/imageFromURL.png`, await image.encode())
Ver código de este ejemplo
Resultado:
El método es el mismo composite
. En este caso obtenemos la imagen y la
codificamos para poder convertirla en un objeto Image manipulable.
ℹ️ Debemos asegurarnos que el archivo que vamos a codificar es de tipo imagen y su extensión es soportada (jpg, jpeg, png, webp).
Ajustar una imagen al contenedor
La API cuenta con el método fit
:
// tutorial/4-fitImage.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
// Primero le indicamos el tamaño de la caja donde debe ajustarse
lakeImage.fit(image.width, image.height)
// La renderizamos en nuestro lienzo
image.composite(lakeImage, 0, 0)
await Deno.writeFile(`./tutorial/output/fitImage.png`, await image.encode())
Ver código de este ejemplo
Resultado:
Este método recibe dos parámetros: un ancho y un alto. Estos determinan el
tamaño de una caja delimitadora. La imagen se centrará y ajustará a ese tamaño.
En este caso estamos cargando el ancho y alto de nuestro lienzo, simulando
que el lienzo es el contenedor padre donde tiene que ajustarse. Luego lo
renderizamos con composite
.
📄 Referencia: Image#fit
Contener una imagen
Similar a la propiedad object-fit: contain
de CSS, podemos crear una caja
delimitadora y hacer que la imagen se posicione dentro de ella sin crear ningun
recorte:
// tutorial/5-containImage.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
lakeImage.contain(image.width, image.height)
image.composite(lakeImage)
await Deno.writeFile(
`./tutorial/output/imageContained.png`,
await image.encode(),
)
Ver código de este ejemplo
Resultado:
Podemos darle cualquier ancho y alto, pero al darle las medidas de nuestro
lienzo, estamos haciendo que se delimite de la misma manera allí.
📄 Referencia: Image#contain
Cubrir un espacio con una imagen
Al igual que la propiedad object-fit: cover
de CSS, tenemos el método cover
:
// tutorial/6-coverImage.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
lakeImage.cover(image.width, image.height)
image.composite(lakeImage)
await Deno.writeFile(`./tutorial/output/cover.png`, await image.encode())
Ver código de este ejemplo
Resultado:
📄 Referencia: Image#cover
Renderizar un SVG
Para pintar un SVG en nuestro lienzo podemos usar el método renderSVG
// tutorials/7-renderSVG.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const svgString = await Deno.readTextFile('./tutorial/deno.svg')
image.composite(
Image.renderSVG(svgString, 500, Image.SVG_MODE_WIDTH),
)
await Deno.writeFile(`./tutorial/output/renderSVG.png`, await image.encode())
Ver código de este ejemplo
Resultado:
El método renderSVG
recibe 3 parámetros:
- El SVG como string: obligatorio
- Un número para escalarlo / un número de ancho / un número de alto
- El modo de redimensionamiento a usar (SVG_MODE_SCALE, SVG_MODE_WIDTH, SVG_MODE_HEIGHT): opcional.
Con las combinaciones se obtienen distintos resultados. Por ejemplo:
Image.renderSVG(svgString, 500, Image.SVG_MODE_WIDTH)
Determina que el ancho del SVG debe ser 500. Se mantiene la relación de aspecto del svg.
📄 Referencia: Image#renderSVG
Cortar una imagen
Utiliando el método crop
:
// tutorials/8-cropImage.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
image.composite(
await Image.decode(await Deno.readFile('./tutorial/lake.jpg')),
)
// Indicamos desde qué punto y cuáles medidas
image.crop(0, 0, 400, 200)
await Deno.writeFile(
`./tutorial/output/scroppedImage.png`,
await image.encode(),
)
Ver código de este ejemplo
Resultado:
Cortar imagen como círculo
Utilizando el método cropCircle
:
// tutorials/9-cropCircle.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
lakeImage.cropCircle(false)
lakeImage.fit(image.width, image.height)
image.composite(lakeImage)
await Deno.writeFile(
`./tutorial/output/croppedCircle.png`,
await image.encode(),
)
Ver código de este ejemplo
Resultado:
📄 Referencia:
Image#cropCircle
Dibujar cajas y círculos
Para dibujar en este lienzo, podes utilizar drawBox
y drawCircle
indicandole
los parámetros para las coordenadas y las medidas de los elementos.
// tutorials/10-drawing.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
image.composite(lakeImage)
// Indicamos las coordenadas, las medidas y el color
const box = new Image(200, 200).drawBox(0, 0, 200, 200, 0xFF0000FF)
// Indicamos las coordenadas, el radio y el color
const circle = new Image(200, 200).drawCircle(100, 100, 100, 0x00FF00FF)
image.composite(box)
image.composite(circle)
await Deno.writeFile(`./tutorial/output/drawing.png`, await image.encode())
Ver código de este ejemplo
Resultado:
📄 Referencia:
Redimensionar las imágenes
Esta función permite darle la dimensión deseada a una imagen. Podemos indicar ademas si queremos preservar la relación de aspecto:
// tutorials/11-resizing.ts
import { Image } from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
const lakeImage = await Image.decode(await Deno.readFile('./tutorial/lake.jpg'))
// Indicamos las medidas
lakeImage.resize(600, Image.RESIZE_AUTO)
await Deno.writeFile(`./tutorial/output/resized.png`, await lakeImage.encode())
Ver código de este ejemplo
Resultado:
El método recibe ancho y alto, sin embargo podemos especificar uno sólo,
junto con el modo automático para que se preverse la relación de aspecto, como
en el ejemplo.
📄 Referencia: Image#resize
Renderizar texto
Para escribir en nuestro lienzo, podemos usar Image.renderText()
. Esta función
nos creará una imagen con el texto que le indiquemos, así podemos añadirla a
nuestro lienzo con composite
.
// tutorials/12-renderText.ts
import {
Image,
TextLayout,
} from 'https://deno.land/x/imagescript@1.2.15/mod.ts'
import createImage from './template.ts'
const image = createImage()
// Con la clase TextLayout definimos algunos parámetros para nuestro texto
const textLayout = new TextLayout({
maxWidth: 800,
maxHeight: 300,
wrapStyle: 'word',
})
// Cargamos nuestra fuente
const font = await Deno.readFile('./tutorial/Lato.ttf')
// Enviamos la fuente, el tamaño de la letra, nuestro texto, el color y la configuración (es opcional)
const textImage = Image.renderText(font, 56, 'My text!', 0xFFFFFFFF, textLayout)
image.composite(textImage)
await Deno.writeFile(`./tutorial/output/renderText.png`, await image.encode())
Ver código de este ejemplo
Resultado:
El método recibe ancho y alto, sin embargo podemos especificar uno sólo,
junto con el modo automático para que se preverse la relación de aspecto, como
en el ejemplo.
📄 Referencia:
Donde más buscar
Esta librería tiene muchos métodos más para explorar, incluso para invertir colores, manejar la iluminación, saturación y opacidad.
Para más información podes consultar la documentación o ingresar a su servidor de Discord para resolver consultas consultas.
Generador de imágenes Polaroid
Para que puedas explorar, usé Deno Fresh para mostrar este ejemplo: crear imagenes polaroid! Super sencillo.
- Deploy: generate-polaroid.deno.dev
- Repositorio: Ver código
#typescript
#deno