Image Token Calculator — TypeScript 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 TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Vision image token-cost calculator (OpenAI-style tile math).
// Pure + deterministic, throws on invalid input - the unit-test surface for
// the Image Token Calculator tool.
export type DetailLevel = 'low' | 'high' | 'auto';
export interface TokenBreakdown {
/** Detail level actually applied. `auto` resolves to `low` or `high`. */
detail: 'low' | 'high';
/** Dimensions after the high-detail downscaling pipeline (identity for low). */
scaledWidth: number;
scaledHeight: number;
/** 512 px tiles along each axis (both 1 in low detail). */
tilesX: number;
tilesY: number;
/** Total 512 px tiles used (tilesX * tilesY). */
tiles: number;
/** Fixed base cost of the low-resolution view, in tokens. */
base: number;
/** Extra tokens for the high-resolution tile views (0 in low detail). */
detailTokens: number;
/** Total estimated tokens: base + detailTokens. */
total: number;
}
/** 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: number, height: number): { width: number; height: number } {
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 {@link 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.
*
* Throws when dimensions are not finite positive integers or the detail level
* is unknown.
*/
export function imageTokens(
width: number,
height: number,
detail: DetailLevel = 'auto'
): TokenBreakdown {
if (!Number.isFinite(width) || !Number.isFinite(height)) {
throw new Error('Width and height must be finite numbers');
}
if (!Number.isInteger(width) || !Number.isInteger(height)) {
throw new Error('Width and height must be whole pixels');
}
if (width <= 0 || height <= 0) {
throw new Error('Width and height must be greater than zero');
}
let resolved: 'low' | 'high';
switch (detail) {
case 'low':
resolved = 'low';
break;
case 'high':
resolved = 'high';
break;
case 'auto':
resolved = width <= AUTO_LOW_MAX && height <= AUTO_LOW_MAX ? 'low' : 'high';
break;
default:
throw new Error(`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 →