Skip to content

Image Token Calculator — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * Image Token Calculator — estimate the vision token cost of an image using
 * OpenAI-style tile math.
 *
 * Language: PHP (8.1+, standard library only)
 * 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 InvalidArgumentException on invalid input
 *     (as the TS reference throws).
 *   - Functionally equivalent to the TS reference: same inputs -> same outputs.
 *   - Self-contained: no extensions beyond core.
 *
 * Rounding note: the TS reference uses Math.round (half up). PHP's round()
 * rounds half away from zero — identical on positives — but itc_round spells
 * it as floor(x + 0.5) for exact parity and returns int.
 */

declare(strict_types=1);

namespace CosmoDev\ImageTokenCalculator;

/** Fixed token cost of the low-resolution image view. */
const LOW_DETAIL_TOKENS = 85;
/** Token cost of one high-resolution 512 px tile. */
const TILE_TOKENS = 170;
/** Images are first scaled to fit inside this square. */
const MAX_SIDE = 2048;
/** Then the shortest side is capped at this length. */
const MAX_SHORT_SIDE = 768;
/** Tile edge length in pixels. */
const TILE_SIZE = 512;
/** Both dimensions at or under this → 'auto' stays low detail. */
const AUTO_LOW_MAX = 512;

/**
 * JS Math.round parity: round half up (values here are always positive).
 */
function itc_round(float $x): int
{
    return (int) \floor($x + 0.5);
}

/**
 * Raise unless the value is an integer greater than zero.
 */
function itc_reject_non_dimension(string $name, mixed $value): void
{
    if (!\is_int($value)) {
        throw new \InvalidArgumentException('Width and height must be whole pixels');
    }
    if ($value <= 0) {
        throw new \InvalidArgumentException('Width and height must be greater than zero');
    }
}

/**
 * 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.
 *
 * @return array{width: int, height: int}
 */
function itc_preprocess_image(int $width, int $height): array
{
    $longest = \max($width, $height);
    if ($longest > MAX_SIDE) {
        $scale = MAX_SIDE / $longest;
        $width = \max(1, itc_round($width * $scale));
        $height = \max(1, itc_round($height * $scale));
    }
    $shortest = \min($width, $height);
    if ($shortest > MAX_SHORT_SIDE) {
        $scale = MAX_SHORT_SIDE / $shortest;
        $width = \max(1, itc_round($width * $scale));
        $height = \max(1, itc_round($height * $scale));
    }
    return ['width' => $width, 'height' => $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 itc_preprocess_image, 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 'low'|'high'|'auto' $detail
 * @return array{detail: 'low'|'high', scaledWidth: int, scaledHeight: int,
 *     tilesX: int, tilesY: int, tiles: int, base: int, detailTokens: int,
 *     total: int}
 * @throws \InvalidArgumentException on non-positive dimensions or an
 *     unknown detail level.
 */
function itc_image_tokens(int $width, int $height, string $detail = 'auto'): array
{
    itc_reject_non_dimension('width', $width);
    itc_reject_non_dimension('height', $height);

    if ($detail === 'low' || $detail === 'high') {
        $resolved = $detail;
    } elseif ($detail === 'auto') {
        $resolved = ($width <= AUTO_LOW_MAX && $height <= AUTO_LOW_MAX) ? 'low' : 'high';
    } else {
        throw new \InvalidArgumentException("Unknown detail level: {$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,
        ];
    }

    $scaled = itc_preprocess_image($width, $height);
    $tilesX = (int) \ceil($scaled['width'] / TILE_SIZE);
    $tilesY = (int) \ceil($scaled['height'] / TILE_SIZE);
    $tiles = $tilesX * $tilesY;
    $detailTokens = $tiles * TILE_TOKENS;

    return [
        'detail' => 'high',
        'scaledWidth' => $scaled['width'],
        'scaledHeight' => $scaled['height'],
        'tilesX' => $tilesX,
        'tilesY' => $tilesY,
        'tiles' => $tiles,
        'base' => LOW_DETAIL_TOKENS,
        'detailTokens' => $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 →