Skip to content

Integration Examples

ts-potrace can be integrated with various frameworks, libraries, and tools to create powerful image processing workflows. This guide provides examples of how to integrate ts-potrace with popular technologies.

Web Applications

React Integration

Using ts-potrace in a React application:

tsx
import React, { useEffect, useState } from 'react'
import { trace } from 'ts-potrace'

// Component that converts an image to SVG
function ImageTracer({ imageUrl }) {
  const [svgContent, setSvgContent] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    // Fetch the image first
    fetch(imageUrl)
      .then(response => response.arrayBuffer())
      .then((buffer) => {
        // Convert the fetched image to SVG using ts-potrace
        trace(Buffer.from(buffer), (err, svg) => {
          setIsLoading(false)
          if (err) {
            setError(err)
          }
          else if (svg) {
            setSvgContent(svg)
          }
        })
      })
      .catch((err) => {
        setIsLoading(false)
        setError(err)
      })
  }, [imageUrl])

  if (isLoading)
    return <div>Converting image...</div>
  if (error) {
    return (
      <div>
        Error:
        {error.message}
      </div>
    )
  }
  if (!svgContent)
    return <div>No SVG generated</div>

  // Render the SVG content
  return <div dangerouslySetInnerHTML={{ __html: svgContent }} />
}

// Usage
function App() {
  return (
    <div className="App">
      <h1>Image Tracer</h1>
      <ImageTracer imageUrl="/path/to/image.png" />
    </div>
  )
}

export default App

Vue.js Integration

Using ts-potrace in a Vue.js application:

vue
<script>
import { trace } from 'ts-potrace'

export default {
  name: 'ImageTracer',
  props: {
    imageUrl: String,
  },
  data() {
    return {
      svgContent: null,
      isLoading: true,
      error: null,
    }
  },
  mounted() {
    // Fetch the image and trace it
    fetch(this.imageUrl)
      .then(response => response.arrayBuffer())
      .then((buffer) => {
        trace(Buffer.from(buffer), (err, svg) => {
          this.isLoading = false
          if (err) {
            this.error = err
          }
          else {
            this.svgContent = svg
          }
        })
      })
      .catch((err) => {
        this.isLoading = false
        this.error = err
      })
  }
}
</script>

<template>
  <div>
    <div v-if="isLoading">
      Converting image...
    </div>
    <div v-else-if="error">
      Error: {{ error.message }}
    </div>
    <div v-else-if="svgContent" v-html="svgContent" />
    <div v-else>
      No SVG generated
    </div>
  </div>
</template>

Server-Side Integration

Express.js Integration

Creating an image conversion API with Express:

ts
import fs from 'node:fs'
import path from 'node:path'
import express from 'express'
import multer from 'multer'
import { posterize, Potrace } from 'ts-potrace'

const app = express()
const upload = multer({ dest: 'uploads/' })

// Simple tracing endpoint
app.post('/api/trace', upload.single('image'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'No image uploaded' })
  }

  const potrace = new Potrace({
    turdSize: Number(req.query.turdSize) || 2,
    alphaMax: Number(req.query.alphaMax) || 1,
    optCurve: req.query.optCurve !== 'false',
    optTolerance: Number(req.query.optTolerance) || 0.2,
    threshold: Number(req.query.threshold) || -1,
    blackOnWhite: req.query.blackOnWhite !== 'false',
    color: req.query.color || 'auto',
    background: req.query.background || 'transparent',
  })

  potrace.loadImage(req.file.path, (err) => {
    // Clean up the uploaded file
    fs.unlink(req.file!.path, () => {})

    if (err) {
      return res.status(500).json({ error: err.message })
    }

    const svg = potrace.getSVG()

    res.setHeader('Content-Type', 'image/svg+xml')
    res.send(svg)
  })
})

// Posterization endpoint
app.post('/api/posterize', upload.single('image'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'No image uploaded' })
  }

  const options = {
    steps: Number(req.query.steps) || 5,
    fillStrategy: req.query.fillStrategy || 'dominant',
    rangeDistribution: req.query.rangeDistribution || 'auto',
    background: req.query.background || 'transparent',
  }

  posterize(req.file.path, options, (err, svg) => {
    // Clean up the uploaded file
    fs.unlink(req.file!.path, () => {})

    if (err) {
      return res.status(500).json({ error: err.message })
    }

    res.setHeader('Content-Type', 'image/svg+xml')
    res.send(svg)
  })
})

app.listen(3000, () => {
  console.log('Server running on port 3000')
})

Serverless Function Integration

Using ts-potrace in an AWS Lambda function:

ts
import { Readable } from 'node:stream'
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { trace } from 'ts-potrace'

const s3Client = new S3Client({ region: 'us-east-1' })

// Convert S3 object stream to buffer
async function streamToBuffer(stream: Readable): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = []
    stream.on('data', chunk => chunks.push(Buffer.from(chunk)))
    stream.on('error', reject)
    stream.on('end', () => resolve(Buffer.concat(chunks)))
  })
}

export async function handler(event: any) {
  try {
    // Get the source bucket and key from the event
    const srcBucket = event.Records[0].s3.bucket.name
    const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '))

    // Get the object from S3
    const { Body } = await s3Client.send(
      new GetObjectCommand({
        Bucket: srcBucket,
        Key: srcKey,
      })
    )

    if (!Body) {
      throw new Error('Failed to get object from S3')
    }

    // Convert stream to buffer
    const imageBuffer = await streamToBuffer(Body as Readable)

    // Trace the image
    return new Promise((resolve, reject) => {
      trace(imageBuffer, (err, svg) => {
        if (err) {
          return reject(err)
        }

        // Destination key - replace extension with .svg
        const dstKey = srcKey.replace(/\.[^/.]+$/, '.svg')

        // Upload the SVG to S3
        s3Client.send(
          new PutObjectCommand({
            Bucket: srcBucket,
            Key: dstKey,
            Body: svg,
            ContentType: 'image/svg+xml',
          })
        )
          .then(() => {
            resolve({
              statusCode: 200,
              body: JSON.stringify({
                message: 'Image converted successfully',
                source: srcKey,
                result: dstKey,
              }),
            })
          })
          .catch(reject)
      })
    })
  }
  catch (error) {
    console.error('Error:', error)
    return {
      statusCode: 500,
      body: JSON.stringify({
        message: 'Error processing image',
        error: (error as Error).message,
      }),
    }
  }
}

Desktop Applications

Electron Integration

Using ts-potrace in an Electron application:

ts
import fs from 'node:fs'
import path from 'node:path'
// In the main process (main.js)
import { app, BrowserWindow, dialog, ipcMain } from 'electron'
import { trace } from 'ts-potrace'

// Create a function to handle image tracing
function traceImage(filePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    trace(filePath, {
      optCurve: true,
      turdSize: 2,
    }, (err, svg) => {
      if (err)
        reject(err)
      else if (svg)
        resolve(svg)
      else reject(new Error('Failed to generate SVG'))
    })
  })
}

// Handle IPC message from renderer
ipcMain.handle('trace-image', async (event, filePath) => {
  try {
    const svg = await traceImage(filePath)
    return { success: true, svg }
  }
  catch (error) {
    return { success: false, error: (error as Error).message }
  }
})

// Handle save SVG request
ipcMain.handle('save-svg', async (event, svg) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: 'Save SVG File',
    defaultPath: 'traced-image.svg',
    filters: [{ name: 'SVG Files', extensions: ['svg'] }],
  })

  if (!canceled && filePath) {
    fs.writeFileSync(filePath, svg)
    return { success: true, filePath }
  }

  return { success: false }
})

// Rest of Electron app initialization...
tsx
import { ipcRenderer } from 'electron'
// In the renderer process (renderer.tsx if using React)
import React, { useState } from 'react'

function ImageTracerApp() {
  const [selectedImage, setSelectedImage] = useState<string | null>(null)
  const [svg, setSvg] = useState<string | null>(null)
  const [isProcessing, setIsProcessing] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    if (file) {
      setSelectedImage(file.path)
      setSvg(null)
      setError(null)
    }
  }

  const handleTrace = async () => {
    if (!selectedImage)
      return

    setIsProcessing(true)
    setError(null)

    try {
      const result = await ipcRenderer.invoke('trace-image', selectedImage)
      if (result.success) {
        setSvg(result.svg)
      }
      else {
        setError(result.error)
      }
    }
    catch (err) {
      setError((err as Error).message)
    }
    finally {
      setIsProcessing(false)
    }
  }

  const handleSave = async () => {
    if (!svg)
      return

    try {
      const result = await ipcRenderer.invoke('save-svg', svg)
      if (result.success) {
        alert(`SVG saved to: ${result.filePath}`)
      }
    }
    catch (err) {
      setError((err as Error).message)
    }
  }

  return (
    <div>
      <h1>Electron Image Tracer</h1>

      <div>
        <input type="file" accept="image/*" onChange={handleFileSelect} />
        <button onClick={handleTrace} disabled={!selectedImage || isProcessing}>
          {isProcessing ? 'Processing...' : 'Trace Image'}
        </button>
      </div>

      {error && <div className="error">{error}</div>}

      {svg && (
        <div>
          <div dangerouslySetInnerHTML={{ __html: svg }} />
          <button onClick={handleSave}>Save SVG</button>
        </div>
      )}
    </div>
  )
}

export default ImageTracerApp

Build Tools Integration

Webpack / Vite Plugin

A simple webpack loader for converting images to SVG at build time:

ts
import loaderUtils from 'loader-utils'
// ts-potrace-loader.js
import { trace } from 'ts-potrace'

export default function loader(source) {
  const callback = this.async()
  const options = loaderUtils.getOptions(this) || {}

  trace(source, options, (err, svg) => {
    if (err) {
      callback(err)
    }
    else {
      // Return the SVG as a module
      callback(null, `export default ${JSON.stringify(svg)}`)
    }
  })
}

// Set the loader to handle binary files
export const raw = true

In your webpack config:

js
// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        oneOf: [
          {
            // For files with .svg.png extension, use the ts-potrace loader
            resourceQuery: /trace/,
            use: [
              {
                loader: 'ts-potrace-loader',
                options: {
                  turdSize: 2,
                  optCurve: true,
                }
              }
            ]
          },
          // For other images, use regular file-loader
          {
            loader: 'file-loader',
          }
        ]
      }
    ]
  }
}

Usage in your code:

js
// Import a traced SVG version of the image
import tracedLogo from './logo.png?trace'

// Use the traced SVG
document.getElementById('logo-container').innerHTML = tracedLogo

Released under the MIT License.