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++ (C++17, 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; invalid input throws std::invalid_argument (as
//     the TS reference throws).
//   - Functionally equivalent to the TS reference: same inputs -> same outputs.
//   - Self-contained: std only (no third-party dependencies).
//
// Rounding note: the TS reference uses Math.round (half up); shrink spells it
// as std::floor(x + 0.5) for exact parity.

#include <cmath>
#include <stdexcept>
#include <string>
#include <utility>

namespace cosmodev {
namespace image_token_calculator {

/// Fixed token cost of the low-resolution image view.
inline constexpr unsigned kLowDetailTokens = 85;
/// Token cost of one high-resolution 512 px tile.
inline constexpr unsigned kTileTokens = 170;
/// Images are first scaled to fit inside this square.
inline constexpr unsigned kMaxSide = 2048;
/// Then the shortest side is capped at this length.
inline constexpr unsigned kMaxShortSide = 768;
/// Tile edge length in pixels.
inline constexpr unsigned kTileSize = 512;
/// Both dimensions at or under this -> `Auto` stays low detail.
inline constexpr unsigned kAutoLowMax = 512;

/// Requested detail mode of an image (`Auto` mirrors the TS default).
enum class DetailLevel { Low, High, Auto };

/// Mirrors the `TokenBreakdown` interface in the TS lib.
struct TokenBreakdown {
    /// Detail level actually applied (`Auto` resolves to "low" or "high").
    std::string 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;
};

/// A width x height pair returned by `preprocessImage`.
struct Dimensions {
    unsigned width;
    unsigned height;
};

namespace internal {

/// JS `Math.round` parity, floored at 1 px: half up, never zero.
inline unsigned shrink(unsigned side, double scale)
{
    double v = std::floor(static_cast<double>(side) * scale + 0.5);
    return v < 1.0 ? 1u : static_cast<unsigned>(v);
}

/// ceil(n / d) for positive integers, without floating point.
inline unsigned ceilDiv(unsigned n, unsigned d)
{
    return (n + d - 1) / d;
}

} // namespace internal

/// Scale `(width, height)` per the vision preprocessing pipeline:
/// 1. fit inside a `kMaxSide` x `kMaxSide` square (longest side capped), then
/// 2. cap the shortest side at `kMaxShortSide`.
///
/// Aspect ratio is preserved; each step is skipped when already satisfied.
inline Dimensions preprocessImage(unsigned width, unsigned height)
{
    unsigned w = width;
    unsigned h = height;
    unsigned longest = w > h ? w : h;
    if (longest > kMaxSide) {
        double scale = static_cast<double>(kMaxSide) / static_cast<double>(longest);
        w = internal::shrink(w, scale);
        h = internal::shrink(h, scale);
    }
    unsigned shortest = w < h ? w : h;
    if (shortest > kMaxShortSide) {
        double scale = static_cast<double>(kMaxShortSide) / static_cast<double>(shortest);
        w = internal::shrink(w, scale);
        h = internal::shrink(h, scale);
    }
    return Dimensions{w, h};
}

/// Estimate the token cost of a `width` x `height` image at the given detail
/// level.
///
/// - `DetailLevel::Low`: fixed `kLowDetailTokens`, whatever the size.
/// - `DetailLevel::High`: the image is downscaled by `preprocessImage`,
///   tiled into `kTileSize` squares, and each tile costs `kTileTokens` on top
///   of the base.
/// - `DetailLevel::Auto`: low when both dimensions are <= `kAutoLowMax`,
///   otherwise high.
///
/// Throws std::invalid_argument for zero dimensions or an unknown detail
/// level.
inline TokenBreakdown imageTokens(unsigned width, unsigned height, DetailLevel detail)
{
    if (width == 0 || height == 0) {
        throw std::invalid_argument("Width and height must be greater than zero");
    }

    bool resolved_high;
    switch (detail) {
    case DetailLevel::Low:
        resolved_high = false;
        break;
    case DetailLevel::High:
        resolved_high = true;
        break;
    case DetailLevel::Auto:
        resolved_high = width > kAutoLowMax || height > kAutoLowMax;
        break;
    }

    if (!resolved_high) {
        return TokenBreakdown{
            "low", width, height, 1, 1, 1, kLowDetailTokens, 0, kLowDetailTokens};
    }

    Dimensions scaled = preprocessImage(width, height);
    unsigned tiles_x = internal::ceilDiv(scaled.width, kTileSize);
    unsigned tiles_y = internal::ceilDiv(scaled.height, kTileSize);
    unsigned tiles = tiles_x * tiles_y;
    unsigned detail_tokens = tiles * kTileTokens;
    return TokenBreakdown{
        "high",
        scaled.width,
        scaled.height,
        tiles_x,
        tiles_y,
        tiles,
        kLowDetailTokens,
        detail_tokens,
        kLowDetailTokens + detail_tokens};
}

/// Parse a detail-level string ("low" | "high" | "auto") — the bridge from
/// the TS string union to `DetailLevel`.
inline DetailLevel parseDetail(const std::string& detail)
{
    if (detail == "low") {
        return DetailLevel::Low;
    }
    if (detail == "high") {
        return DetailLevel::High;
    }
    if (detail == "auto") {
        return DetailLevel::Auto;
    }
    throw std::invalid_argument("Unknown detail level: " + detail);
}

} // namespace image_token_calculator
} // namespace cosmodev

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 →