Image Token Calculator — C 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 C 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: C (C11, standard library only; links against libm for floor)
* 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; invalid input is reported through a status code,
* never a crash.
* - Functionally equivalent to the TS reference: same inputs -> same outputs.
* - Self-contained: stdlib only (no external dependencies).
*
* Rounding note: the TS reference uses Math.round (half up); shrink spells it
* as floor(x + 0.5) for exact parity.
*/
#include <math.h>
#include <string.h>
/* Fixed token cost of the low-resolution image view. */
#define LOW_DETAIL_TOKENS 85u
/* Token cost of one high-resolution 512 px tile. */
#define TILE_TOKENS 170u
/* Images are first scaled to fit inside this square. */
#define MAX_SIDE 2048u
/* Then the shortest side is capped at this length. */
#define MAX_SHORT_SIDE 768u
/* Tile edge length in pixels. */
#define TILE_SIZE 512u
/* Both dimensions at or under this -> "auto" stays low detail. */
#define AUTO_LOW_MAX 512u
/* Requested detail mode of an image (ITK_AUTO mirrors the TS default). */
typedef enum {
ITK_LOW = 0,
ITK_HIGH,
ITK_AUTO
} detail_level;
/* Status codes: why an estimate was rejected (the TS reference throws). */
typedef enum {
ITK_OK = 0,
/* Width/height must be integers greater than zero. */
ITK_ERR_DIMENSIONS,
/* The detail level string was not "low", "high", or "auto". */
ITK_ERR_DETAIL
} image_token_status;
/* Mirrors the TokenBreakdown interface in the TS lib. */
typedef struct {
/* Detail level actually applied ("low" or "high"). */
const char *detail;
/* Dimensions after the high-detail downscaling pipeline (identity for low). */
unsigned scaled_width;
unsigned scaled_height;
/* 512 px tiles along each axis (both 1 in low detail). */
unsigned tiles_x;
unsigned tiles_y;
/* Total 512 px tiles used (tiles_x * tiles_y). */
unsigned tiles;
/* Fixed base cost of the low-resolution view, in tokens. */
unsigned base;
/* Extra tokens for the high-resolution tile views (0 in low detail). */
unsigned detail_tokens;
/* Total estimated tokens: base + detail_tokens. */
unsigned total;
} token_breakdown;
/* JS Math.round parity, floored at 1 px: half up, never zero. */
static unsigned shrink(unsigned side, double scale)
{
double v = floor((double)side * scale + 0.5);
return v < 1.0 ? 1u : (unsigned)v;
}
/* ceil(n / d) for positive integers, without floating point. */
static unsigned ceil_div(unsigned n, unsigned d)
{
return (n + d - 1u) / d;
}
/* Scale (width, height) per the vision preprocessing pipeline:
*
* 1. fit inside a MAX_SIDE x 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.
*/
void preprocess_image(unsigned width, unsigned height,
unsigned *out_width, unsigned *out_height)
{
unsigned w = width;
unsigned h = height;
unsigned longest = w > h ? w : h;
if (longest > MAX_SIDE) {
double scale = (double)MAX_SIDE / (double)longest;
w = shrink(w, scale);
h = shrink(h, scale);
}
unsigned shortest = w < h ? w : h;
if (shortest > MAX_SHORT_SIDE) {
double scale = (double)MAX_SHORT_SIDE / (double)shortest;
w = shrink(w, scale);
h = shrink(h, scale);
}
*out_width = w;
*out_height = h;
}
/* Estimate the token cost of a width x height image at a detail level.
*
* - ITK_LOW: fixed LOW_DETAIL_TOKENS, whatever the size.
* - ITK_HIGH: the image is downscaled by preprocess_image, tiled into
* TILE_SIZE squares, and each tile costs TILE_TOKENS on top of the base.
* - ITK_AUTO: low when both dimensions are <= AUTO_LOW_MAX, else high.
*
* Returns ITK_ERR_DIMENSIONS for zero dimensions, ITK_ERR_DETAIL for an
* unknown detail level.
*/
image_token_status image_tokens(unsigned width, unsigned height,
detail_level detail,
token_breakdown *out)
{
if (width == 0u || height == 0u) {
return ITK_ERR_DIMENSIONS;
}
int resolved_high;
switch (detail) {
case ITK_LOW:
resolved_high = 0;
break;
case ITK_HIGH:
resolved_high = 1;
break;
case ITK_AUTO:
resolved_high = width > AUTO_LOW_MAX || height > AUTO_LOW_MAX;
break;
default:
return ITK_ERR_DETAIL;
}
if (!resolved_high) {
*out = (token_breakdown){
.detail = "low",
.scaled_width = width,
.scaled_height = height,
.tiles_x = 1u,
.tiles_y = 1u,
.tiles = 1u,
.base = LOW_DETAIL_TOKENS,
.detail_tokens = 0u,
.total = LOW_DETAIL_TOKENS,
};
return ITK_OK;
}
unsigned scaled_width;
unsigned scaled_height;
preprocess_image(width, height, &scaled_width, &scaled_height);
unsigned tiles_x = ceil_div(scaled_width, TILE_SIZE);
unsigned tiles_y = ceil_div(scaled_height, TILE_SIZE);
unsigned tiles = tiles_x * tiles_y;
unsigned detail_tokens = tiles * TILE_TOKENS;
*out = (token_breakdown){
.detail = "high",
.scaled_width = scaled_width,
.scaled_height = scaled_height,
.tiles_x = tiles_x,
.tiles_y = tiles_y,
.tiles = tiles,
.base = LOW_DETAIL_TOKENS,
.detail_tokens = detail_tokens,
.total = LOW_DETAIL_TOKENS + detail_tokens,
};
return ITK_OK;
}
/* Parse a detail-level string ("low" | "high" | "auto") — the bridge from
* the TS string union to detail_level. */
image_token_status parse_detail(const char *detail, detail_level *out)
{
if (detail == NULL) {
return ITK_ERR_DETAIL;
}
if (strcmp(detail, "low") == 0) {
*out = ITK_LOW;
} else if (strcmp(detail, "high") == 0) {
*out = ITK_HIGH;
} else if (strcmp(detail, "auto") == 0) {
*out = ITK_AUTO;
} else {
return ITK_ERR_DETAIL;
}
return ITK_OK;
}
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 →