Skip to content

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 →