@patrickisgreat/string-art-engine
v1.0.0
Published
Framework-agnostic string art generation engine using WebAssembly
Maintainers
Readme
String Art Engine
A high-performance, framework-agnostic string art generation engine powered by WebAssembly and Rust. Transform any image into beautiful string art with advanced algorithms and customizable parameters.
Features
- High Performance: WebAssembly-powered Rust algorithms for fast processing
- Framework Agnostic: Works with React, Vue, Angular, vanilla JS, or any TypeScript/JavaScript project
- Highly Configurable: Extensive parameters for fine-tuning results
- Advanced Algorithms: Smart preprocessing, quality analysis, and optimization
- Parallel Processing: Multi-threaded processing for complex images
- Precise Control: Line count optimization and thread thickness simulation
- Quality Analysis: Automatic image suitability assessment
Installation
npm install string-art-engineQuick Start
import { StringArtEngine } from 'string-art-engine';
// Create engine instance
const engine = new StringArtEngine({
pinCount: 200,
maxLineCount: 4000,
lineWeight: 0.25
});
// Process an image
async function generateStringArt(imageData: ImageData) {
const result = await engine.processImage(imageData);
console.log(`Generated ${result.lineCount} lines`);
console.log('Pin coordinates:', result.pinCoordinates);
console.log('Line sequence:', result.lineSequence);
return result;
}Advanced Usage
Custom Configuration
import { StringArtEngine, DEFAULT_CONFIG } from 'string-art-engine';
const engine = new StringArtEngine({
...DEFAULT_CONFIG,
pinCount: 300, // More pins = finer detail
maxLineCount: 6000, // More lines = more detail
lightnessPenalty: 0.8, // Higher = more emphasis on dark areas
qualityMode: 'enhanced', // Better quality at cost of speed
useParallelProcessing: true // Use multiple threads
}, {
enableLogging: true, // Enable console logging
useWorker: true, // Use web worker (recommended)
onProgress: (progress) => {
console.log(`${progress.stage}: ${progress.progress}%`);
}
});Image Quality Analysis
// Analyze image before processing
const analysis = await engine.getQualityAnalysis(imageData);
console.log(`Quality Score: ${(analysis.score * 100).toFixed(1)}%`);
console.log(`Recommendation: ${analysis.recommendation}`);
// Preprocess low-quality images
if (analysis.score < 0.5) {
const preprocessed = await engine.preprocessImage(imageData);
if (preprocessed) {
imageData = preprocessed;
}
}Line Count Optimization
// Generate with specific line count
const result = await engine.recalculateWithLineCount(imageData, 3000);
// Update configuration dynamically
engine.updateConfig({
lineWeight: 0.3,
lightnessPenalty: 0.9
});API Reference
StringArtEngine
Constructor
constructor(config?: Partial<StringArtConfig>, options?: StringArtEngineOptions)Methods
processImage(imageData: ImageData): Promise<StringArtResult>
Process an image to generate string art.
recalculateWithLineCount(imageData: ImageData, targetLines: number): Promise<StringArtResult>
Generate string art with a specific target line count.
analyzeImageQuality(imageData: ImageData): Promise<number>
Analyze image quality (returns 0-1 score).
getQualityAnalysis(imageData: ImageData): Promise<ImageQualityAnalysis>
Get detailed quality analysis with recommendations.
preprocessImage(imageData: ImageData): Promise<ImageData | null>
Preprocess image for better string art quality.
updateConfig(config: Partial<StringArtConfig>): void
Update engine configuration.
destroy(): void
Clean up resources.
Configuration Options
interface StringArtConfig {
pinCount: number; // Number of pins (50-500)
imageSize: number; // Image size in pixels
minDistance: number; // Min distance between pins
maxLineCount: number; // Maximum lines to generate
lineWeight: number; // Line opacity (0-1)
lightnessPenalty: number; // Dark area emphasis (0-1)
lineNorm: number; // Penalty calculation method
useImportanceWeighting: boolean; // Use importance maps
qualityMode: 'standard' | 'enhanced' | 'multipass' | 'parallel';
useParallelProcessing: boolean; // Enable multithreading
numThreads?: number; // Number of threads
}Result Structure
interface StringArtResult {
lineSequence: number[]; // Pin sequence for drawing
pinCoordinates: [number, number][]; // Pin positions
processingTime: number; // Time taken (ms)
lineCount: number; // Actual lines generated
qualityScore?: number; // Input quality score
}Examples
React Component
import React, { useState, useRef } from 'react';
import { StringArtEngine } from 'string-art-engine';
function StringArtGenerator() {
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const engineRef = useRef(new StringArtEngine());
const handleImageUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
setLoading(true);
// Convert file to ImageData
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = async () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const stringArtResult = await engineRef.current.processImage(imageData);
setResult(stringArtResult);
setLoading(false);
};
img.src = URL.createObjectURL(file);
};
return (
<div>
<input type="file" accept="image/*" onChange={handleImageUpload} />
{loading && <p>Generating string art...</p>}
{result && (
<div>
<p>Generated {result.lineCount} lines in {result.processingTime.toFixed(0)}ms</p>
{/* Render string art using result.lineSequence and result.pinCoordinates */}
</div>
)}
</div>
);
}Canvas Rendering
function renderStringArt(canvas: HTMLCanvasElement, result: StringArtResult) {
const ctx = canvas.getContext('2d');
const { lineSequence, pinCoordinates } = result;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Set line style
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.lineWidth = 0.5;
// Draw lines
for (let i = 0; i < lineSequence.length - 1; i++) {
const fromPin = lineSequence[i];
const toPin = lineSequence[i + 1];
const from = pinCoordinates[fromPin];
const to = pinCoordinates[toPin];
ctx.beginPath();
ctx.moveTo(from[0], from[1]);
ctx.lineTo(to[0], to[1]);
ctx.stroke();
}
}Building from Source
# Install dependencies
npm install
# Build WASM module
npm run build:wasm
# Build TypeScript
npm run build:ts
# Build everything
npm run buildRequirements
- Node.js 16+
- Modern browser with WebAssembly support
- For building: Rust toolchain with
wasm-pack
Performance Tips
- Use Web Workers: Enable
useWorker: truefor non-blocking processing - Optimize Pin Count: Start with 200 pins, increase for more detail
- Quality vs Speed: Use 'standard' mode for speed, 'enhanced' for quality
- Parallel Processing: Enable for complex images with many pins
- Image Preprocessing: Use
preprocessImage()for low-quality inputs
Browser Support
- Chrome 57+
- Firefox 52+
- Safari 11+
- Edge 16+
License
MIT
Contributing
Contributions welcome! Please read our contributing guidelines and submit pull requests to our repository.
Credits
Built with ❤️ by the StringRing team. Powered by Rust, WebAssembly, and modern web technologies.
