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
