# vintageJS: a canvas filter for retro photos, rewritten in TypeScript

> vintageJS applies a set of image effects to a source image or canvas and hands back the result as a data URL, a canvas or an image element. The 2.3.0 rewrite moved it from Flow and Browserify to TypeScript and Vite without changing the public API, which is the interesting part of the release.

**rendro/vintageJS** — Add a retro/vintage effect to images using the HTML5 canvas element

- Repository: https://github.com/rendro/vintageJS
- Website: https://vintagejs.com/
- Stars: 844 · Forks: 112
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/rendro-vintagejs

## One function, one options object, one promise

The API is a single function. You call vintagejs with a source, which can be a URL, an ImageElement or a CanvasElement, and an effect object holding the options, and you get back a Promise that resolves to a result object. Nothing is written back to the DOM for you, and nothing is mutated in place. The result object exposes three ways to get at the processed pixels. getDataURL returns a data url of the updated image, which the README suggests for updating the source of an existing image. getCanvas returns the canvas with the updated image, for drawing onto another canvas. genImage returns a promise that resolves to an HTMLImageElement of the updated image. The mimeType and quality arguments are optional, and when not provided mimeType defaults to image/jpeg and quality defaults to 1. That is a small, predictable surface, and it is the reason the library can be dropped into a page without a framework.

## Effects are typed, and the type is the documentation

The option list is published as a TypeScript type, which is unusually complete for a project this size. Each entry carries its own default in a comment:

```javascript
type TEffect = {
  curves: false | TCurve,     // default: false
  screen: false | TRGBAColor, // default: false
  saturation: number,         // float between 0 and 1, default: 1
  vignette: number,           // float between 0 and 1, default: 0
  lighten: number,            // float between 0 and 1, default: 0
  viewfinder: false | string, // string must be URL, default: false
  sepia: boolean,             // default: false
  gray: boolean,              // default: false
  brightness: number,         // float between -1 and 1, default: 0
  contrast: number,           // float between -1 and 1, default: 0
};
```

The two compound options are described by their own types. curves is a look up table for colour mappings with one array per channel, r, g and b, each of length 256 holding values between 0 and 255. screen is an RGBA colour with three integer channels and an alpha between 0 and 1. A curve therefore gives you arbitrary per-channel remapping rather than a fixed filter, and screen is a single tint laid over the image. The effect list is small and generic, and the tint and curve options are where the actual control lives.

## A promise chain that ends in the DOM

The usage example is short enough to read in one go, and it shows the intended shape of a call:

```javascript
vintagejs('./path/to/picture.jpg', { brightness: 0.2 })
  .then(res => res.genImage())
  .then(img => document.body.appendChild(img));
```

The first promise resolves to the result object, genImage returns another promise for the image element, and the element is appended. Two other patterns are documented. You can use an image as the source and update it in place through the data url:

```javascript
const srcEl = document.querySelector('img.myImage');
vintagejs(srcEl, { brightness: 0.2 })
  .then(res => {
    srcEl.src = res.getDataURL();
  });
```

Or you can take a canvas as the source and draw the result back onto it:

```javascript
const srcEl = document.querySelector('canvas.myCanvas');
const ctx = srcEl.getContext('2d');
vintagejs(srcEl, { brightness: 0.2 })
  .then(res => {
    ctx.drawImage(res.getCanvas(), 0, 0, srcEl.width, srcEl.height);
  });
```

There is no error handling in any of these examples. The function returns a promise, so a failed image load rejects it, and what you do with that rejection is up to you.

## The 2.3.0 rewrite changed the toolchain, not the API

The changelog entry for 2.3.0 is the most recent and the most consequential. The toolchain moved from Flow plus Browserify to TypeScript plus Vite, and the library is now written in TypeScript and ships .d.ts type declarations. An ES module build at dist/vintage.mjs was added alongside the existing UMD bundle at dist/vintage.js and the minified bundle at dist/vintage.min.js. The changelog also records that all 31 npm audit vulnerabilities, 5 critical and 9 high among them, were fixed by removing the legacy build and test dependencies, and that no runtime dependency was affected. Finally, no API changes: the UMD global vintagejs and the function signature are unchanged. The manifest matches. The main field points at the UMD build, module at the ES module, types at dist/index.d.ts, and the exports map lists types, import and require conditions. So the migration is a build system change with a maintained compatibility surface, which is the right way to do it.

## One runtime dependency, and a browser support caveat

The dependency list is short enough to read at a glance. There is exactly one runtime dependency, nullthrows at 1.0.0, and everything else is dev-only: TypeScript 5.6.3, Vite 7.3.6 and Vitest 3.2.7. The build script runs vite build, then vite build with the min mode, then tsc, and the test script is vitest run. The browser support section is where the caveats live. The README points at caniuse.com/canvas to check support for the canvas element itself, and notes that you get higher performance when canvas blend modes are supported, pointing at the canvas blending feature page, but that fallbacks are implemented. The blend mode question matters because the screen effect, the tint, is a blend operation. On a browser without canvas blending, the same effect still renders through a fallback path, presumably at a lower cost, and the README does not say which browsers fall into which group, so that is a page you have to check yourself.

## The npm package, the demo folder and the licence

Installation is a single npm command:

```bash
$ npm install vintagejs
```

The published files array includes dist and src, so the TypeScript sources ship alongside the bundles, and the repository also carries a bower.json, a package-lock.json, a vite.config.ts, a tsconfig.json and a test directory. The examples folder is a plain HTML page with example.js, an index.html, a couple of sample photographs and a film-1.jpg used as the viewfinder overlay. That last file is the viewfinder option in action: you pass a URL to an image and it is composited over the result as a frame. The licence is MIT, and the project is published under the vintagejs name on npm, with a project page at vintagejs.com. The examples folder is the fastest way to see the effects without wiring anything up.

## Latest release tags lag the package version

The maintenance picture is uneven, and it is worth being precise about it. The repository is not archived and the last push was on 2026-07-14. The published GitHub releases are old: 2.2.0 from 2017-10-20 and 2.0.1 from 2017-08-18. The package version in package.json is 2.3.0, and the changelog documents a 2.3.0 entry covering the TypeScript migration, the ES module build and the audit fix, so the work exists in the repository even though the newest tagged release is 2.2.0. In practice you install from npm, where the version reflects the manifest, and the tags are not what you consume. The changelog itself also shows the shape of the project over time: 1.0.0 initial release, 1.1.0 and 1.1.1 performance work, 1.1.2 AngularJS support and a grunt based build, 2.0.0 a rewrite from ground up with a functional API, 2.1.0 string sources, 2.2.0 true grayscale, and now the TypeScript move. Treat 2.3.0 as the version to read the source of, and do not expect new effects to arrive on a schedule.

## Conclusion

Adopt vintageJS if you need a browser-side retro filter and you can work with a canvas pipeline and a promise-based result object, and if you are comfortable with an ES module build served over the web. Do not adopt it for server-side image processing, since the whole implementation depends on the canvas element and its blend modes, and do not expect the effect list to grow quickly, because the last push was on 2026-07-14 and the newest published GitHub release is 2.2.0 from 2017 while the package version is 2.3.0. Before you commit, check the browser support notes for the canvas element and for canvas blend modes, and pin the exact version you install.

## FAQ

### How do I install and use vintageJS?

Install it with npm install vintagejs. The library exposes a single function that takes a source (a URL, an ImageElement or a CanvasElement) and an effect options object, and returns a promise resolving to a result object with getDataURL, getCanvas and genImage methods.

### What image effects does vintageJS support?

The documented options are curves, screen, saturation, vignette, lighten, viewfinder, sepia, gray, brightness and contrast. Curves takes a per-channel look up table of 256 values, and screen takes an RGBA colour with an alpha value.

### Does vintageJS need canvas blend mode support?

The README says higher performance is available when canvas blend modes are supported, but that fallbacks are implemented. The canvas element itself is the baseline requirement, and the README links to caniuse.com for both checks.

### What changed in vintageJS 2.3.0?

The toolchain moved from Flow and Browserify to TypeScript and Vite, the library now ships .d.ts type declarations and an ES module build at dist/vintage.mjs, and 31 npm audit vulnerabilities were removed with the legacy dependencies. The README states there were no API changes and the UMD global vintagejs is unchanged.

## Sources

- [License: MIT](https://github.com/rendro/vintageJS/blob/master/LICENSE)
- [Project website](https://vintagejs.com/)
- [README](https://github.com/rendro/vintageJS/blob/master/README.md)
- [Releases](https://github.com/rendro/vintageJS/releases)
- [rendro/vintageJS on GitHub](https://github.com/rendro/vintageJS)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rendro-vintagejs
