Skip to content

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 →