> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinfoil.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Image processing

> Learn how to use Tinfoil for image processing with multimodal models.

## Image Upload

> [!WARNING]
>
> **Multimodal Models Only:** Image processing requires models with vision capabilities. Currently, **Kimi K3.1 Pro**, **Gemma 4 31B**, and **DeepSeek V4.1 Flash** support image inputs. Other models (Llama, GPT-OSS) are text-only and cannot process images.

> [!NOTE]
>
> See the [vision models](https://docs.tinfoil.sh/models/vision.md) and [chat models](https://docs.tinfoil.sh/models/chat.md) pages for complete model specifications and multimodal capabilities.

### How It Works

Image processing works through the chat/completions endpoint using base64-encoded images. Images are sent as data URLs in the message content alongside your text prompt.

### Converting Images to Base64

There are several ways to convert your images to base64 format:

```bash Command Line theme={"dark"}
# Convert image to base64
base64 -i image.jpg -o image_base64.txt

# Or use it directly in your terminal
base64 image.jpg | pbcopy  # Copies to clipboard on macOS
```

```python Python theme={"dark"}
import base64

def image_to_base64(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

# Usage
base64_string = image_to_base64("image.jpg")
```

```javascript JavaScript theme={"dark"}
import fs from 'fs';

function imageToBase64(imagePath) {
    const imageBuffer = fs.readFileSync(imagePath);
    return imageBuffer.toString('base64');
}

// Usage
const base64String = imageToBase64('image.jpg');
```

```go Go theme={"dark"}
package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "log"
    "os"
)

func imageToBase64(imagePath string) (string, error) {
    file, err := os.Open(imagePath)
    if err != nil {
        return "", err
    }
    defer file.Close()

    data, err := io.ReadAll(file)
    if err != nil {
        return "", err
    }

    return base64.StdEncoding.EncodeToString(data), nil
}

func main() {
    // Usage
    base64String, err := imageToBase64("image.jpg")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Base64 encoded image:", base64String[:50], "...") // Print first 50 chars
}
```

```rust Rust theme={"dark"}
// The Tinfoil Rust SDK ships ImageUrl::from_path which reads the file,
// detects the MIME type, and returns a ready-to-use ImageUrl with a
// base64 data URL. Use it directly in chat messages — no manual encoding.
use tinfoil::chat::ImageUrl;
use tinfoil::multimodal::ImageUrlExt;

let image = ImageUrl::from_path("image.jpg")?;
println!("Base64 data URL: {}...", &image.url[..50.min(image.url.len())]);
```

### API Usage

```python Python theme={"dark"}
from tinfoil import TinfoilAI
import base64
import mimetypes
from pathlib import Path

# Initialize client with Gemma 4 vision model
client = TinfoilAI(
    api_key="<YOUR_API_KEY>",
)

# Read and encode image with proper MIME type detection
image_path = "image.jpg"
with open(image_path, "rb") as image_file:
    base64_image = base64.b64encode(image_file.read()).decode('utf-8')

# Determine MIME type
mime_type, _ = mimetypes.guess_type(image_path)
if not mime_type or not mime_type.startswith('image/'):
    # Fallback based on file extension
    ext = Path(image_path).suffix.lower()
    mime_type_map = {
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.gif': 'image/gif',
        '.webp': 'image/webp'
    }
    mime_type = mime_type_map.get(ext, 'image/jpeg')

# Create completion with multimodal content
response = client.chat.completions.create(
    model="gemma4-31b",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {
                "type": "image_url",
                "image_url": {
                    "url": f"data:{mime_type};base64,{base64_image}"
                }
            }
        ]
    }]
)

print(response.choices[0].message.content)
```

```typescript JavaScript theme={"dark"}
import { TinfoilAI } from 'tinfoil';
import fs from 'fs';
import path from 'path';

// Initialize client with Gemma 4 vision model
const client = new TinfoilAI({
  apiKey: process.env.TINFOIL_API_KEY
});

// Read and encode image
const imagePath = 'image.jpg';
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');

// Determine MIME type
const ext = path.extname(imagePath).toLowerCase();
const mimeTypes: Record<string, string> = {
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.png': 'image/png',
  '.gif': 'image/gif',
  '.webp': 'image/webp'
};
const mimeType = mimeTypes[ext] || 'image/jpeg';

try {
  const response = await client.chat.completions.create({
    model: 'gemma4-31b',
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: "What's in this image?" },
          {
            type: 'image_url',
            image_url: {
              url: `data:${mimeType};base64,${base64Image}`
            }
          }
        ] as any
      }
    ]
  });

  console.log(response.choices[0]?.message?.content);
} catch (error) {
  console.error('Image processing failed:', error);
}
```

```go Go theme={"dark"}
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "log"
    "mime"
    "os"
    "path/filepath"
    "strings"

    "github.com/openai/openai-go/v3"
    "github.com/openai/openai-go/v3/option"
    "github.com/tinfoilsh/tinfoil-go"
)

func main() {
    // Initialize client with Gemma 4 vision model
    client, err := tinfoil.NewClient(
        option.WithAPIKey(os.Getenv("TINFOIL_API_KEY")),
    )
    if err != nil {
        log.Fatal(err)
    }

    // Read and encode image
    imagePath := "image.jpg"
    imageData, err := os.ReadFile(imagePath)
    if err != nil {
        log.Fatal(err)
    }

    base64Image := base64.StdEncoding.EncodeToString(imageData)

    // Determine MIME type
    ext := filepath.Ext(imagePath)
    mimeType := mime.TypeByExtension(ext)

    // Robust fallback for MIME type
    if mimeType == "" || !strings.HasPrefix(mimeType, "image/") {
        // Fallback based on file extension
        mimeTypeMap := map[string]string{
            ".jpg":  "image/jpeg",
            ".jpeg": "image/jpeg",
            ".png":  "image/png",
            ".gif":  "image/gif",
            ".webp": "image/webp",
        }

        if fallbackType, ok := mimeTypeMap[strings.ToLower(ext)]; ok {
            mimeType = fallbackType
        } else {
            mimeType = "image/jpeg" // final fallback
        }
    }

    // Create completion with multimodal content
    imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Image)

    response, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
        Model: "gemma4-31b",
        Messages: []openai.ChatCompletionMessageParamUnion{
            openai.UserMessage([]openai.ChatCompletionContentPartUnionParam{
                openai.TextContentPart("What's in this image?"),
                openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{
                    URL: imageURL,
                }),
            }),
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(response.Choices[0].Message.Content)
}
```

```rust Rust theme={"dark"}
use tinfoil::chat::{
    ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImage,
    ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent,
    ChatCompletionRequestUserMessageContentPart, CreateChatCompletionRequestArgs, ImageUrl,
};
use tinfoil::multimodal::ImageUrlExt;
use tinfoil::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new_default().await?;

    // ImageUrl::from_path handles base64 encoding and MIME detection.
    let image = ImageUrl::from_path("image.jpg")?;

    let request = CreateChatCompletionRequestArgs::default()
        .model("gemma4-31b")
        .messages(vec![ChatCompletionRequestMessage::User(
            ChatCompletionRequestUserMessage {
                content: ChatCompletionRequestUserMessageContent::Array(vec![
                    ChatCompletionRequestUserMessageContentPart::Text(
                        "What's in this image?".into(),
                    ),
                    ChatCompletionRequestUserMessageContentPart::ImageUrl(
                        ChatCompletionRequestMessageContentPartImage { image_url: image },
                    ),
                ]),
                name: None,
            },
        )])
        .build()?;

    let response = client.chat().create(request).await?;
    println!("{}", response.choices[0].message.content.as_deref().unwrap_or(""));
    Ok(())
}
```

### Best Practices

1. **Image Size**: For optimal performance, resize large images before processing (recommended max: 4096x4096)
2. **Base64 Encoding**: Ensure proper base64 encoding and include the correct MIME type in the data URL
3. **Multiple Images**: You can include multiple images in a single chat completion by adding multiple image\_url objects to the content array
4. **Compression**: Consider compressing large images to reduce payload size and improve response times


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.