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# 12 / .NET 8, 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 ArgumentException (as the
// TS reference throws).
// - Functionally equivalent to the TS reference: same inputs -> same outputs.
// - Self-contained: the BCL only (no NuGet packages).
//
// Rounding note: the TS reference uses Math.round (half up); Shrink spells it
// as Math.Floor(x + 0.5) for exact parity.
namespace CosmoDev.Polyglot;
/// <summary>Requested detail mode of an image (<see cref="DetailLevel.Auto"/>
/// mirrors the TS default).</summary>
public enum DetailLevel
{
Low,
High,
Auto,
}
/// <summary>Mirrors the <c>TokenBreakdown</c> interface in the TS lib.</summary>
/// <param name="Detail">Detail level actually applied ("low" or "high").</param>
/// <param name="ScaledWidth">Dimensions after the high-detail downscaling
/// pipeline (identity for low).</param>
/// <param name="ScaledHeight">See <paramref name="ScaledWidth"/>.</param>
/// <param name="TilesX">512 px tiles along each axis (both 1 in low detail).</param>
/// <param name="TilesY">See <paramref name="TilesX"/>.</param>
/// <param name="Tiles">Total 512 px tiles used (<c>TilesX * TilesY</c>).</param>
/// <param name="Base">Fixed base cost of the low-resolution view, in tokens.</param>
/// <param name="DetailTokens">Extra tokens for the high-resolution tile views
/// (0 in low detail).</param>
/// <param name="Total">Total estimated tokens: <c>Base + DetailTokens</c>.</param>
public sealed record TokenBreakdown(
string Detail,
int ScaledWidth,
int ScaledHeight,
int TilesX,
int TilesY,
int Tiles,
int Base,
int DetailTokens,
int Total);
/// <summary>Image Token Calculator — estimate the vision token cost of an
/// image using OpenAI-style tile math.</summary>
public static class ImageTokenCalculator
{
/// <summary>Fixed token cost of the low-resolution image view.</summary>
public const int LowDetailTokens = 85;
/// <summary>Token cost of one high-resolution 512 px tile.</summary>
public const int TileTokens = 170;
/// <summary>Images are first scaled to fit inside this square.</summary>
public const int MaxSide = 2048;
/// <summary>Then the shortest side is capped at this length.</summary>
public const int MaxShortSide = 768;
/// <summary>Tile edge length in pixels.</summary>
public const int TileSize = 512;
/// <summary>Both dimensions at or under this -> <see cref="DetailLevel.Auto"/>
/// stays low detail.</summary>
public const int AutoLowMax = 512;
/// <summary>JS <c>Math.round</c> parity, floored at 1 px: half up, never zero.</summary>
private static int Shrink(int side, double scale) =>
Math.Max(1, (int)Math.Floor(side * scale + 0.5));
/// <summary>ceil(n / d) for positive integers, without floating point.</summary>
private static int CeilDiv(int n, int d) => (n + d - 1) / d;
/// <summary>Scale (width, height) per the vision preprocessing pipeline:
/// 1. fit inside a <see cref="MaxSide"/> x <see cref="MaxSide"/> square
/// (longest side capped), then 2. cap the shortest side at
/// <see cref="MaxShortSide"/>. Aspect ratio is preserved; each step is
/// skipped when already satisfied.</summary>
public static (int Width, int Height) PreprocessImage(int width, int height)
{
int w = width;
int h = height;
int longest = Math.Max(w, h);
if (longest > MaxSide)
{
double scale = (double)MaxSide / longest;
w = Shrink(w, scale);
h = Shrink(h, scale);
}
int shortest = Math.Min(w, h);
if (shortest > MaxShortSide)
{
double scale = (double)MaxShortSide / shortest;
w = Shrink(w, scale);
h = Shrink(h, scale);
}
return (w, h);
}
/// <summary>Estimate the token cost of a width x height image at the given
/// detail level.
///
/// - <see cref="DetailLevel.Low"/>: fixed <see cref="LowDetailTokens"/>,
/// whatever the size.
/// - <see cref="DetailLevel.High"/>: the image is downscaled by
/// <see cref="PreprocessImage"/>, tiled into <see cref="TileSize"/>
/// squares, and each tile costs <see cref="TileTokens"/> on top of the
/// base.
/// - <see cref="DetailLevel.Auto"/>: low when both dimensions are
/// <= <see cref="AutoLowMax"/>, otherwise high.
///
/// Throws <see cref="ArgumentException"/> for non-positive dimensions or
/// an unknown detail level.</summary>
public static TokenBreakdown ImageTokens(int width, int height, DetailLevel detail)
{
if (width <= 0 || height <= 0)
{
throw new ArgumentException("Width and height must be greater than zero");
}
bool resolvedHigh = detail switch
{
DetailLevel.Low => false,
DetailLevel.High => true,
DetailLevel.Auto => width > AutoLowMax || height > AutoLowMax,
_ => throw new ArgumentException($"Unknown detail level: {detail}"),
};
if (!resolvedHigh)
{
return new TokenBreakdown(
Detail: "low",
ScaledWidth: width,
ScaledHeight: height,
TilesX: 1,
TilesY: 1,
Tiles: 1,
Base: LowDetailTokens,
DetailTokens: 0,
Total: LowDetailTokens);
}
(int scaledWidth, int scaledHeight) = PreprocessImage(width, height);
int tilesX = CeilDiv(scaledWidth, TileSize);
int tilesY = CeilDiv(scaledHeight, TileSize);
int tiles = tilesX * tilesY;
int detailTokens = tiles * TileTokens;
return new TokenBreakdown(
Detail: "high",
ScaledWidth: scaledWidth,
ScaledHeight: scaledHeight,
TilesX: tilesX,
TilesY: tilesY,
Tiles: tiles,
Base: LowDetailTokens,
DetailTokens: detailTokens,
Total: LowDetailTokens + detailTokens);
}
/// <summary>Parse a detail-level string ("low" | "high" | "auto") — the
/// bridge from the TS string union to <see cref="DetailLevel"/>.</summary>
public static DetailLevel ParseDetail(string detail) => detail switch
{
"low" => DetailLevel.Low,
"high" => DetailLevel.High,
"auto" => DetailLevel.Auto,
_ => throw new ArgumentException($"Unknown detail level: {detail}"),
};
}
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 →