Skip to content

A javascript library to turn any image into a colorful letter composition

Notifications You must be signed in to change notification settings

BjrInt/enluminure.js

Repository files navigation

enluminure.js

"Enluminure" /ɑ̃.ly.mi.nyʁ/ is a french word meaning the technique of illuminating manuscript with flourishes and drawings in order to beautify the text content.

Although the french term means creating a picture alongside a written text, this javascript library turns any picture into a a composition of colorful letters. It uses canvases internally to perform the conversion, so it only works inside of browsers.

How does it work

Enluminure is a really simple algorithm that takes its inspiration from ASCII art. It works by first slicing a source picture into tiles. These tiles are then iterated over and their lightness is extracted. When the extracting process is done, the rendering starts by assigning a letter from a given pool (a word, a random chain of characters, ...) for each of the tiles. This letter will be as bright as the source tile, but with a different hue.

This is as simple as that, but sometimes the simpler solutions are usually the most effective.

Below you'll find a detail from a Boticelli painting and some enluminures rendering of it.

Usage

Setup

The library is available as a Typescript library in the src directory. To compile it to ES6 into a dist directory, run the following commandes after cloning.

# Install the dependencies
npm install

# Creates a directory named dist with an "enluminure.js" file
npm run compile

You can also use Vite bundler provided in the dependencies to run the example file by running :

npm run dev

Basics

Creates an enluminure from an image and adding it to the DOM using an <img> tag:

import Enluminure from './src/enluminure'

const enluminure = new Enluminure()
await enluminure.loadImage('./path/to/img.jpg')

const img = document.createElement('img')
img.src = enluminure.render('image/png')
document.body.appendChild(img)

Using a worker thread

Since render can take a while it could be useful to spawn a dedicated worker thread to process the rendering so the main execution thread is not blocked.

main.js:

const enluminure = new Enluminure()
await enluminure.loadImage('./path/to/img.jpg')

const renderWorker = new Worker('render.js')
renderWorker.postMessage(enluminure)
renderWorker.onmessage = ({ data }) => {
  const img = document.createElement('img')
  img.src = data
  document.body.appendChild(img)
}

render.js:

onmessage = ({ data }) => {
  const img = data.render()
  postMessage(img)
}

Using an MVVM framework (ex: React)

import Enluminure from './src/lib/ts/enluminure'

const component = () => {
  const [src, setSrc] = useState(null)
  const handleChange = e => {
    const f = e.target.files[0]

    const enluminure = new Enluminure()
    await enluminure.loadImage(f)
    setSrc( enluminure.render('image/jpeg') )
  }

  return (
    <div>
      <input type="file" onChange={handleChange} />
      { src && <img {src} /> }
    </div>
  )
}

Options

You can specify options from the start using the constructor or using the setOptions method. There are several options available. Make sure to fine tune each of them. Possibilities are nearly endless, from suggestive numeric art to near-copy of the source, there is a lot of room to explore in between!

All parameters give deterministic output excepted:

  • Those used with the RANDOM preset (hueRotation, characterDistribution)
  • The jitter function

This means you can easily predict an output based on input parameters.

Name Values Default Value Description
backgroundColor A color string (Hex, rgb(), hsl()) '#000000' The background color of the generated enluminure
characterDistribution see CharacterDistibutions enum ROW Defines how each letter is pasted onto the enluminure. On ROW mode each character from the characterPool is laid from left to right, on COLUMN from top to bottom, on RANDOM in no specific order
characterPool A string 'ËNłÛMÍИЦR€' A set of letters to generate the enluminure
focusGradientOpacity A number from 0 (transparent) to 1 (opaque) 0 Creates a radial gradient on top of the enluminure to better highlight the center of the image
fontFamily A valid font string 'monospace' Defines the font used to generate the enluminure
fontSize A number 6 Defines the size of the font on the enluminure
hueMax A number between 0 and 360 60 The enluminure will be drawn using a color palette, between [hueMin, hueMax]. You can restrict the generated picture to a certain part of the spectrum using these two parameters
hueMin A number between 0 and 360 0 (see hueMax)
hueRotation see HueRotations enum LINEAR_FORWARD Defines how the colors are distributed on the enluminure. The hue is selected from the HSL circle, either on a forward motion, a back and forth motion, a random value, or with a trigonometric function crafting scattered hues on the generated enluminure.
jitterProbability A number between 0 and 1 0 The probability that the jitter function (see maxJitterOffset) gets triggered.
  • 0 = no trigger
  • 1 = triggers for every character of the enluminure
luminanceFactor Number 100 The luminance factor defines the scaling of the Lightness parameter in the HSL circle. Greater value will give brighter images and greater contrasts, smaller value will give more colorful images
maxJitterOffsetX number 0 The jitter function will move a character from it's original position to a randomly assigned position along the X-axis. This parameter gives the maximum distance, in px, by which the character can be moved from it's original offset. For the jitter function to be triggered you should also check the jitterProbability option
maxJitterOffsetY number 0 The jitter function will move a character from it's original position to a randomly assigned position along the Y-axis. This parameter gives the maximum distance, in px, by which the character can be moved from it's original offset. For the jitter function to be triggered you should also check the jitterProbability option
saturation A number between 0 and 100 100 This parameter controls the overall saturation of the enluminure. 100 will give you satured images across the whole color spectrum, 0 will render a greyscale enluminure.
tileSize Any number (greater than 0) 6 Enluminures are generated by iterating over the source picture at a given scale, this parameter specifies the size of each step. Smaller values will give you more detailed image (at the expense of more processing time).
⚠️ The generated enluminure will always be a discrete multiple of tileSize, so the result might be smaller than the source picture

Methods

constructor(options?: object)

Initialize the enluminure with a set of (optional) parameters.

const defaultEnluminure = new Enluminure()
const customEnluminure = new Enluminre({
  jitterChance: 0.2,
  maxJitterOffsetX: 50,
  maxJitterOffsetY: 10,
  saturation: 20,
})

async loadImage(source: path | File | Blob)

Loads a reference image for the enluminure. If the file is succesfully loaded returns the dimensions of the enluminure to render.

getDimensions()

Returns the dimensions of the enluminure to be generated. The size of the enluminure could be slightly smaller than the source picture, since it is always a multiple of the tileSize parameter.

getOptions()

Returns the enluminure current options.

Using a file input

const enluminure = new Enluminure()
const fileInput = document.createElement('input')
fileInput.type = 'file'
fileInput.addEventListener('change', async e => {
  await enluminure.loadImage(e.target.files[0])

  const result = enluminure.render()
  const img = document.createElement('img')
  img.src = result
  document.body.appendChild(result)
})
document.body.appendChild(fileInput)

Using file path

const enluminure = new Enluminure()
await enluminure.loadImage('picture.jpg')
const result = enluminure.render()
const img = document.createElement('img')
img.src = result
document.body.appendChild(result)

setOptions(options: object)

Set options for the generated enluminure after initialization (ex: taking parameters from user input)

const enluminure = new Enluminure()
await enluminure.loadImage('picture.jpg')

document.querySelector('input[type="range"]').addEventListener('change', e => {
  enluminure.setOptions({ saturation: e.value })
})

render(output?: string)

Renders the enluminure and returns the result as a data URL. You can specify a custom mimetype for the rendered image. The default values depends of your browser.

⚠️ This process could take some time depending on the tileSize and the dimensions of the source picture. For CPU intensive task, it is reccomended to delegate the rendering in a dedicated web worker.

About

A javascript library to turn any image into a colorful letter composition

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published