Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - SiluPanda/vision-prep: Resize and optimize images for vision LLM APIs · GitHub
Skip to content

Repository files navigation

vision-prep

Resize, optimize, and encode images for vision LLM APIs -- with provider-specific token estimation and ready-to-use content blocks for OpenAI, Anthropic, and Gemini.

npm versionnpm downloadslicensenodetypes


Description

Every major vision LLM provider has different image sizing rules, format requirements, file size limits, and token cost formulas. OpenAI divides images into 512x512 tiles and charges per tile. Anthropic scales images to fit within 1568px on the longest side and charges based on pixel count. Gemini charges a flat 258 tokens per image regardless of size.

vision-prep handles all of this in a single function call. Given an image source (file path, URL, Buffer, Uint8Array, or base64 string) and a target provider, it:

  • Detects the image format from magic bytes (no file extension required)
  • Extracts dimensions directly from image headers without full decode
  • Validates format support and file size against provider constraints
  • Computes the effective dimensions after provider-specific resize logic
  • Encodes the image to base64
  • Estimates the vision token cost using each provider's documented formula
  • Returns a provider-formatted content block ready to embed in a messages array

Zero runtime dependencies. Pure Node.js. Full TypeScript support with strict mode.


Installation

npm install vision-prep

Requires Node.js 18 or later.


Quick Start

import{prepare,estimateTokens,createPreparer}from'vision-prep';// Prepare an image for OpenAI (from a file path)constresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});console.log(result.tokens);// 765console.log(result.mimeType);// 'image/jpeg'console.log(result.contentBlock);// Ready for OpenAI messages array// Estimate tokens without full processingconstestimate=awaitestimateTokens({width: 1920,height: 1080},'openai',{detail: 'high'},);console.log(estimate.tokens);// 1105// Create a reusable preparer for Anthropicconstprep=createPreparer({provider: 'anthropic'});constanthropicResult=awaitprep.prepare(imageBuffer);console.log(anthropicResult.contentBlock);// Ready for Anthropic messages array

Features

  • Multi-provider support -- OpenAI, Anthropic, and Gemini with provider-specific resize logic, token formulas, and content block formats.
  • Format detection from magic bytes -- Identifies PNG, JPEG, GIF, WebP, and BMP from binary headers. No reliance on file extensions.
  • Header-only dimension extraction -- Reads width and height from image headers (IHDR for PNG, SOF for JPEG, etc.) without decoding pixel data.
  • Accurate token estimation -- Implements each provider's documented token formula: tile-based for OpenAI, pixel-based for Anthropic, flat rate for Gemini.
  • Provider-formatted content blocks -- Returns content blocks in the exact structure each provider's API expects, ready for direct embedding in a messages array.
  • Flexible image sources -- Accepts file paths, HTTP/HTTPS URLs, Buffers, Uint8Arrays, raw base64 strings, and data URLs.
  • Batch processing -- Process multiple images in parallel with configurable concurrency and aggregate statistics.
  • Factory pattern -- createPreparer() returns a pre-configured instance to avoid repeating provider and option arguments.
  • Zero runtime dependencies -- Built entirely on Node.js built-in modules (node:fs, node:path, global fetch).
  • Full TypeScript -- Strict mode, exported type definitions, declaration maps.

API Reference

prepare(image, provider, options?)

Prepare a single image for a vision LLM API. Detects format, extracts dimensions, validates against provider constraints, encodes to base64, estimates tokens, and returns a PreparedImage with a provider-formatted content block.

functionprepare(image: ImageSource,provider: Provider,options?: PrepareOptions,): Promise<PreparedImage>;

Parameters:

ParameterTypeDescription
imageImageSourceFile path, URL, Buffer, Uint8Array, base64 string, or data URL.
providerProviderTarget provider: 'openai', 'anthropic', or 'gemini'.
optionsPrepareOptionsOptional configuration (see PrepareOptions).

Returns:Promise<PreparedImage> -- see PreparedImage.

import{prepare}from'vision-prep';// From a file pathconstresult=awaitprepare('./photo.jpg','openai',{detail: 'high'});// From a URLconstresult=awaitprepare('https://example.com/image.png','anthropic');// From a Bufferconstresult=awaitprepare(imageBuffer,'gemini');// From a data URLconstresult=awaitprepare('data:image/jpeg;base64,/9j/4AAQ...','openai');

prepareForOpenAI(image, options?)

Convenience wrapper equivalent to prepare(image, 'openai', options).

functionprepareForOpenAI(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForAnthropic(image, options?)

Convenience wrapper equivalent to prepare(image, 'anthropic', options).

functionprepareForAnthropic(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

prepareForGemini(image, options?)

Convenience wrapper equivalent to prepare(image, 'gemini', options).

functionprepareForGemini(image: ImageSource,options?: PrepareOptions,): Promise<PreparedImage>;

estimateTokens(image, provider, options?)

Estimate vision token cost without full image preparation. Accepts either an image source (from which dimensions are extracted) or a { width, height } object for direct calculation.

functionestimateTokens(image: ImageSource|{width: number;height: number},provider: Provider,options?: EstimateOptions,): Promise<TokenEstimate>;

Parameters:

ParameterTypeDescription
imageImageSource | { width: number; height: number }Image source or dimensions object.
providerProviderTarget provider.
optionsEstimateOptionsOptional. detail for OpenAI ('low', 'high', 'auto'), model for cost estimation.

Returns:Promise<TokenEstimate> -- see TokenEstimate.

import{estimateTokens}from'vision-prep';// From dimensions (no I/O required)constest=awaitestimateTokens({width: 1024,height: 768},'openai',{detail: 'high'});console.log(est.tokens);// 765// From a Buffer (reads dimensions from headers)constest2=awaitestimateTokens(imageBuffer,'anthropic');console.log(est2.tokens);// ceil(width * height / 750)

prepareBatch(images, provider, options?)

Process multiple images in parallel with concurrency control and aggregate statistics.

functionprepareBatch(images: ImageSource[],provider: Provider,options?: BatchPrepareOptions,): Promise<BatchResult>;

Parameters:

ParameterTypeDescription
imagesImageSource[]Array of image sources.
providerProviderTarget provider.
optionsBatchPrepareOptionsOptional. Includes concurrency (default: 4) and continueOnError (default: false).

Returns:Promise<BatchResult> -- see BatchResult.

import{prepareBatch}from'vision-prep';constbatch=awaitprepareBatch([buffer1,buffer2,'./photo.jpg'],'anthropic',{concurrency: 4,continueOnError: true},);console.log(batch.succeeded);// 3console.log(batch.failed);// 0console.log(batch.totalTokens);// Aggregate across all imagesconsole.log(batch.totalOriginalBytes);console.log(batch.totalOptimizedBytes);

createPreparer(config)

Factory function that returns a pre-configured ImagePreparer instance. Avoids repeating provider and option arguments across multiple calls.

functioncreatePreparer(config: PreparerConfig): ImagePreparer;

The returned ImagePreparer exposes three methods: prepare, prepareBatch, and estimateTokens. Options passed to individual method calls are merged with (and override) the config defaults.

import{createPreparer}from'vision-prep';constprep=createPreparer({provider: 'openai',detail: 'high'});// Uses provider='openai' and detail='high' from configconstresult=awaitprep.prepare(imageBuffer);// Override detail for this specific callconstlowResult=awaitprep.prepare(imageBuffer,{detail: 'low'});console.log(lowResult.tokens);// 85// Batch processing with the same configconstbatch=awaitprep.prepareBatch([buf1,buf2],{concurrency: 2});// Token estimationconstest=awaitprep.estimateTokens({width: 1920,height: 1080});

detectFormat(buffer)

Detect image format from magic bytes. Supports PNG, JPEG, GIF, WebP (VP8, VP8L, VP8X), and BMP.

functiondetectFormat(buffer: Buffer|Uint8Array,): 'jpeg'|'png'|'gif'|'webp'|'bmp'|null;

Returns null if the format cannot be identified.


extractDimensions(buffer, format)

Extract image width and height from binary headers without full decode.

functionextractDimensions(buffer: Buffer|Uint8Array,format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): {width: number;height: number}|null;

Returns null if dimensions cannot be extracted (e.g., truncated buffer).


getImageInfo(buffer)

Detect format and extract full image metadata in one call. Throws if format is unrecognized or dimensions cannot be extracted.

functiongetImageInfo(buffer: Buffer|Uint8Array): ImageInfo;
import{getImageInfo}from'vision-prep';constinfo=getImageInfo(imageBuffer);console.log(info.format);// 'jpeg'console.log(info.width);// 1920console.log(info.height);// 1080console.log(info.sizeBytes);// 245760

formatToMimeType(format)

Convert a format string to its corresponding MIME type.

functionformatToMimeType(format: 'jpeg'|'png'|'gif'|'webp'|'bmp',): ImageMimeType;
InputOutput
'jpeg''image/jpeg'
'png''image/png'
'gif''image/gif'
'webp''image/webp'
'bmp''image/bmp'

Token Estimation Functions

These lower-level functions compute token counts directly from dimensions, without any I/O.

estimateOpenAITokens(width, height, detail?)

functionestimateOpenAITokens(width: number,height: number,detail?: 'low'|'high'|'auto',): number;

Returns 85 for 'low' detail. For 'high' (default), applies the resize logic (fit 2048x2048, then scale shortest side to 768px), then computes ceil(w/512) * ceil(h/512) * 170 + 85.

estimateAnthropicTokens(width, height)

functionestimateAnthropicTokens(width: number,height: number): number;

Applies Anthropic resize (longest side 1568px, max 1,568,000 pixels), then computes ceil(width * height / 750).

estimateGeminiTokens(width, height)

functionestimateGeminiTokens(width: number,height: number): number;

Returns 258 regardless of dimensions.

estimateTokensFromDimensions(width, height, provider, options?)

functionestimateTokensFromDimensions(width: number,height: number,provider: Provider,options?: EstimateOptions,): TokenEstimate;

Dispatches to the correct provider's token formula and returns a full TokenEstimate object.


Resize Functions

These expose the provider-specific resize logic for inspection or custom pipelines.

openAIHighDetailResize(width, height)

functionopenAIHighDetailResize(width: number,height: number,): {width: number;height: number};

Step 1: Fit within 2048x2048. Step 2: Scale shortest side to 768px (only shrinks, never upscales).

anthropicResize(width, height)

functionanthropicResize(width: number,height: number,): {width: number;height: number};

Step 1: Constrain longest side to 1568px. Step 2: Constrain total pixels to 1,568,000.

getProviderResizedDimensions(width, height, provider, detail?)

functiongetProviderResizedDimensions(width: number,height: number,provider: Provider,detail?: 'low'|'high'|'auto',): {width: number;height: number};

Returns the effective dimensions after applying provider-specific resize rules. OpenAI 'low' detail fits within 512x512. Gemini fits within 3600x3600.


Provider Content Block Formatters

formatOpenAIContentBlock(base64, mimeType, detail?)

functionformatOpenAIContentBlock(base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock;

Returns { type: 'image_url', image_url: { url: 'data:{mimeType};base64,{data}', detail } }.

formatAnthropicContentBlock(base64, mimeType)

functionformatAnthropicContentBlock(base64: string,mimeType: ImageMimeType,): AnthropicContentBlock;

Returns { type: 'image', source: { type: 'base64', media_type: '{mimeType}', data: '{base64}' } }.

formatGeminiContentBlock(base64, mimeType)

functionformatGeminiContentBlock(base64: string,mimeType: ImageMimeType,): GeminiContentBlock;

Returns { inlineData: { mimeType: '{mimeType}', data: '{base64}' } }.

formatContentBlock(provider, base64, mimeType, detail?)

functionformatContentBlock(provider: Provider,base64: string,mimeType: ImageMimeType,detail?: 'low'|'high'|'auto',): OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;

Dispatches to the correct provider formatter.


Provider Utility Functions

getMaxFileSize(provider)

functiongetMaxFileSize(provider: Provider): number;
ProviderMax file size
'openai'20 MB (20,971,520 bytes)
'anthropic'5 MB (5,242,880 bytes)
'gemini'20 MB (20,971,520 bytes)

isFormatSupported(provider, format)

functionisFormatSupported(provider: Provider,format: string): boolean;
ProviderSupported formats
'openai'jpeg, png, gif, webp
'anthropic'jpeg, png, gif, webp
'gemini'jpeg, png, gif, webp, bmp

Configuration

PrepareOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode. Only affects OpenAI provider.
qualitynumber85JPEG/WebP compression quality (1--100).
format'jpeg' | 'png' | 'webp'Input formatOutput image format override.
preferWebpbooleanfalsePrefer WebP output for smaller file size.
maxWidthnumber--Custom maximum width. Provider constraints still apply as ceiling.
maxHeightnumber--Custom maximum height. Provider constraints still apply as ceiling.
modelstring--Model identifier for USD cost estimation (e.g., 'gpt-4o').
stripMetadatabooleantrueStrip EXIF metadata from the image.
fetchTimeoutnumber30000Timeout in milliseconds for URL fetching.
signalAbortSignal--AbortSignal for cancellation support.

BatchPrepareOptions

Extends PrepareOptions with:

OptionTypeDefaultDescription
concurrencynumber4Maximum number of images to process concurrently.
continueOnErrorbooleanfalseIf true, continue processing remaining images when one fails.

EstimateOptions

OptionTypeDefaultDescription
detail'low' | 'high' | 'auto''high'OpenAI detail mode.
modelstring--Model identifier for USD cost estimation.

PreparerConfig

Extends PrepareOptions with:

OptionTypeDefaultDescription
providerProvider(required)Target provider: 'openai', 'anthropic', or 'gemini'.

Error Handling

vision-prep throws standard Error instances with descriptive messages. Errors are thrown in these situations:

Unrecognized image format

Thrown by getImageInfo and prepare when the image buffer does not match any known format signature.

Error: Unrecognized image format: could not detect format from magic bytes

Unsupported format for provider

Thrown when the detected format is not supported by the target provider (e.g., BMP on OpenAI or Anthropic).

Error: Format 'bmp' is not supported by openai. Supported formats: jpeg, png, gif, webp

File size exceeds provider limit

Thrown when the image exceeds the provider's maximum file size.

Error: Image size (25000000 bytes) exceeds openai limit of 20971520 bytes (20 MB)

Image file not found

Thrown when a file path is provided but the file does not exist.

Error: Image not found: /path/to/missing.jpg

Failed to read image file

Thrown on file I/O errors other than ENOENT.

Error: Failed to read image file: <system error message>

URL fetch errors

Thrown when fetching an image from a URL fails.

Error: Failed to fetch image: HTTP 404
Error: Image fetch timed out after 30000ms

Invalid data URL

Thrown when a data URL string is malformed.

Error: Invalid data URL: missing comma separator

Dimension extraction failure

Thrown by getImageInfo when format is detected but the buffer is too short to read dimensions.

Error: Could not extract dimensions from jpeg image

Batch errors

When continueOnError is true, failed images are represented as BatchError objects in the results array instead of throwing:

interfaceBatchError{index: number;error: {code: string;// 'PREPARE_FAILED'message: string;};}

When continueOnError is false (default), the first error encountered causes the entire batch to reject.


Advanced Usage

Multi-provider comparison

Prepare the same image for multiple providers to compare token costs:

import{estimateTokens}from'vision-prep';constdims={width: 1920,height: 1080};constopenai=awaitestimateTokens(dims,'openai',{detail: 'high'});constanthropic=awaitestimateTokens(dims,'anthropic');constgemini=awaitestimateTokens(dims,'gemini');console.log(`OpenAI: ${openai.tokens} tokens (${openai.width}x${openai.height})`);console.log(`Anthropic: ${anthropic.tokens} tokens (${anthropic.width}x${anthropic.height})`);console.log(`Gemini: ${gemini.tokens} tokens (${gemini.width}x${gemini.height})`);// OpenAI: 1105 tokens (1365x768)// Anthropic: 1844 tokens (1568x882)// Gemini: 258 tokens (1920x1080)

Custom dimension constraints

Apply your own dimension limits on top of provider constraints:

import{prepare}from'vision-prep';constresult=awaitprepare(largeImage,'openai',{detail: 'high',maxWidth: 800,maxHeight: 600,});// Dimensions are constrained to at most 800x600,// then further constrained by OpenAI's resize rules.

Cancellation with AbortSignal

Cancel URL-based image fetching with an AbortSignal:

import{prepare}from'vision-prep';constcontroller=newAbortController();setTimeout(()=>controller.abort(),5000);try{constresult=awaitprepare('https://example.com/large-image.jpg','openai',{signal: controller.signal,fetchTimeout: 10000,});}catch(err){console.error('Fetch cancelled or timed out:',err.message);}

Batch processing with error tolerance

Process a batch of images, collecting errors without stopping the pipeline:

import{prepareBatch}from'vision-prep';constresult=awaitprepareBatch(imageBuffers,'anthropic',{concurrency: 8,continueOnError: true,});for(constitemofresult.images){if('error'initem){console.error(`Image ${item.index} failed: ${item.error.message}`);}else{console.log(`Image prepared: ${item.width}x${item.height}, ${item.tokens} tokens`);}}console.log(`${result.succeeded}/${result.images.length} succeeded`);console.log(`Total tokens: ${result.totalTokens}`);

Using content blocks directly in API calls

The contentBlock property of PreparedImage is formatted for direct use in provider SDK calls:

import{prepareForOpenAI}from'vision-prep';constimage=awaitprepareForOpenAI(buffer,{detail: 'high'});// Use directly in OpenAI API callconstresponse=awaitopenai.chat.completions.create({model: 'gpt-4o',messages: [{role: 'user',content: [{type: 'text',text: 'What is in this image?'},image.contentBlock,// { type: 'image_url', image_url: { url: '...', detail: 'high' }}],},],});
import{prepareForAnthropic}from'vision-prep';constimage=awaitprepareForAnthropic(buffer);// Use directly in Anthropic API callconstresponse=awaitanthropic.messages.create({model: 'claude-sonnet-4-20250514',messages: [{role: 'user',content: [image.contentBlock,// { type: 'image', source: { type: 'base64', ... }}{type: 'text',text: 'Describe this image.'},],},],});

Token Formulas

Each provider uses a different formula to calculate vision token costs.

OpenAI

Detail modeFormula
'low'85 tokens (flat)
'high'Fit within 2048x2048, then scale shortest side to 768px. Tile into 512x512 patches: ceil(w/512) * ceil(h/512) * 170 + 85

Examples at detail: 'high':

InputAfter resizeTilesTokens
512x512512x5121x1255
1024x7681024x7682x2765
1920x10801365x7683x21105
4000x30001024x7682x2765

Anthropic

Constrain longest side to 1568px, then constrain total pixels to 1,568,000. Token count: ceil(width * height / 750).

InputAfter resizeTokens
256x256256x25688
1024x7681024x7681049
1920x10801568x8821844

Gemini

Flat rate: 258 tokens per image, regardless of dimensions.


TypeScript

vision-prep is written in TypeScript with strict mode enabled. All public types are exported:

importtype{ImageSource,Provider,ImageMimeType,ImageInfo,PrepareOptions,OpenAIPrepareOptions,EstimateOptions,BatchPrepareOptions,PreparerConfig,PreparedImage,TokenEstimate,BatchResult,BatchError,OpenAIContentBlock,AnthropicContentBlock,GeminiContentBlock,ImagePreparer,}from'vision-prep';

Key types

typeImageSource=string|Buffer|Uint8Array;typeProvider='openai'|'anthropic'|'gemini';typeImageMimeType=|'image/jpeg'|'image/png'|'image/gif'|'image/webp'|'image/bmp';interfaceImageInfo{width: number;height: number;format: 'jpeg'|'png'|'gif'|'webp'|'bmp';sizeBytes: number;}interfacePreparedImage{base64: string;mimeType: ImageMimeType;width: number;height: number;tokens: number;cost?: number;bytes: number;original: {width: number;height: number;bytes: number;mimeType: ImageMimeType;};contentBlock: OpenAIContentBlock|AnthropicContentBlock|GeminiContentBlock;provider: Provider;detail?: 'low'|'high';}interfaceTokenEstimate{tokens: number;cost?: number;width: number;height: number;provider: Provider;}interfaceBatchResult{images: Array<PreparedImage|BatchError>;totalTokens: number;totalCost?: number;totalOriginalBytes: number;totalOptimizedBytes: number;succeeded: number;failed: number;}interfaceImagePreparer{prepare(image: ImageSource,options?: PrepareOptions): Promise<PreparedImage>;prepareBatch(images: ImageSource[],options?: BatchPrepareOptions): Promise<BatchResult>;estimateTokens(image: ImageSource|{width: number;height: number},options?: EstimateOptions,): Promise<TokenEstimate>;}

License

MIT

About

Resize and optimize images for vision LLM APIs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages