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 →