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 →