Image Token Calculator — JavaScript source
Estimate the vision token cost of an image before sending it to an LLM - low/high/auto detail modes, the 512px tile math, the 2048/768 downscaling steps, and a full base + tiles + detail breakdown. Runs entirely in your browser.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* Image Token Calculator — estimate the vision token cost of an image using
* OpenAI-style tile math.
*
* Language: JavaScript (ES2020+, ES module; runs unmodified in Node 16+
* and modern browsers)
* Source: CosmoDev polyglot showcase port of the Image Token Calculator
* tool, ported from src/lib/imageTokenCalculator.ts (the canonical
* TypeScript implementation).
* Live at: https://dev.cosmolabs.org/tools/image-token-calculator
* License: display source — part of CosmoDev's polyglot tool pages.
*
* Design goals:
* - Pure + deterministic; throws TypeError on invalid input (as the TS
* reference does).
* - Functionally equivalent to the TS reference: same inputs -> same outputs.
* - Self-contained: no dependencies.
*/
/** Fixed token cost of the low-resolution image view. */
export const LOW_DETAIL_TOKENS = 85;
/** Token cost of one high-resolution 512 px tile. */
export const TILE_TOKENS = 170;
/** Images are first scaled to fit inside this square. */
export const MAX_SIDE = 2048;
/** Then the shortest side is capped at this length. */
export const MAX_SHORT_SIDE = 768;
/** Tile edge length in pixels. */
export const TILE_SIZE = 512;
/** Both dimensions at or under this → 'auto' stays low detail. */
export const AUTO_LOW_MAX = 512;
/**
* Scale (width, height) per the vision preprocessing pipeline:
* 1. fit inside a MAX_SIDE × MAX_SIDE square (longest side capped), then
* 2. cap the shortest side at MAX_SHORT_SIDE.
* Aspect ratio is preserved; each step is skipped when already satisfied.
*/
export function preprocessImage(width, height) {
const longest = Math.max(width, height);
if (longest > MAX_SIDE) {
const scale = MAX_SIDE / longest;
width = Math.max(1, Math.round(width * scale));
height = Math.max(1, Math.round(height * scale));
}
const shortest = Math.min(width, height);
if (shortest > MAX_SHORT_SIDE) {
const scale = MAX_SHORT_SIDE / shortest;
width = Math.max(1, Math.round(width * scale));
height = Math.max(1, Math.round(height * scale));
}
return { width, height };
}
/**
* Estimate the token cost of a width × height image at the given detail
* level.
*
* - 'low': fixed LOW_DETAIL_TOKENS, whatever the size.
* - 'high': the image is downscaled by preprocessImage, tiled into TILE_SIZE
* squares, and each tile costs TILE_TOKENS on top of the base.
* - 'auto': low when both dimensions are <= AUTO_LOW_MAX, otherwise high.
*
* @param {number} width
* @param {number} height
* @param {('low'|'high'|'auto')} [detail='auto']
* @returns {{
* detail: 'low'|'high', scaledWidth: number, scaledHeight: number,
* tilesX: number, tilesY: number, tiles: number,
* base: number, detailTokens: number, total: number
* }}
*/
export function imageTokens(width, height, detail = 'auto') {
if (!Number.isFinite(width) || !Number.isFinite(height)) {
throw new TypeError('Width and height must be finite numbers');
}
if (!Number.isInteger(width) || !Number.isInteger(height)) {
throw new TypeError('Width and height must be whole pixels');
}
if (width <= 0 || height <= 0) {
throw new TypeError('Width and height must be greater than zero');
}
let resolved;
if (detail === 'low' || detail === 'high') {
resolved = detail;
} else if (detail === 'auto') {
resolved = width <= AUTO_LOW_MAX && height <= AUTO_LOW_MAX ? 'low' : 'high';
} else {
throw new TypeError(`Unknown detail level: ${String(detail)}`);
}
if (resolved === 'low') {
return {
detail: 'low',
scaledWidth: width,
scaledHeight: height,
tilesX: 1,
tilesY: 1,
tiles: 1,
base: LOW_DETAIL_TOKENS,
detailTokens: 0,
total: LOW_DETAIL_TOKENS,
};
}
const scaled = preprocessImage(width, height);
const tilesX = Math.ceil(scaled.width / TILE_SIZE);
const tilesY = Math.ceil(scaled.height / TILE_SIZE);
const tiles = tilesX * tilesY;
const detailTokens = tiles * TILE_TOKENS;
return {
detail: 'high',
scaledWidth: scaled.width,
scaledHeight: scaled.height,
tilesX,
tilesY,
tiles,
base: LOW_DETAIL_TOKENS,
detailTokens,
total: LOW_DETAIL_TOKENS + detailTokens,
};
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →