Skip to main content
Glama

Interactive Feedback MCP

by zivhdinfo
image-analyzer.js15.3 kB
#!/usr/bin/env node /** * Image Analyzer Module * Provides image analysis capabilities for Interactive Feedback MCP * * Author: STMMO Project * Version: 1.0.0 */ const fs = require('fs-extra'); const path = require('path'); /** * ImageAnalyzer Class * Handles image analysis and content description */ class ImageAnalyzer { constructor() { this.supportedFormats = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; } /** * Analyze uploaded images and provide detailed descriptions * @param {Array} images - Array of image objects with base64 data * @returns {Promise<Array>} Array of analyzed image data */ async analyzeImages(images) { if (!images || !Array.isArray(images) || images.length === 0) { return []; } const analyzedImages = []; for (const image of images) { try { const analysis = await this.analyzeImage(image); analyzedImages.push(analysis); } catch (error) { console.error(`Error analyzing image ${image.name}:`, error); analyzedImages.push({ ...image, analysis: { error: `Failed to analyze image: ${error.message}`, description: 'Image analysis failed', colors: [], objects: [], text: [] } }); } } return analyzedImages; } /** * Analyze a single image * @param {Object} image - Image object with base64 data * @returns {Promise<Object>} Analyzed image data */ async analyzeImage(image) { // Extract metadata from base64 const metadata = this.extractMetadata(image); // Perform basic image analysis const basicAnalysis = this.performBasicAnalysis(image); // Generate content description based on filename and context const contentAnalysis = this.generateContentDescription(image); return { ...image, analysis: { metadata, ...basicAnalysis, ...contentAnalysis, timestamp: new Date().toISOString() } }; } /** * Extract metadata from image * @param {Object} image - Image object * @returns {Object} Metadata information */ extractMetadata(image) { const base64Data = image.base64; const header = base64Data.substring(0, 50); return { format: image.mimeType || 'unknown', originalSize: image.originalSize || 0, compressedSize: image.compressedSize || 0, compressionRatio: image.compressionRatio || '0', base64Length: base64Data.length, estimatedPixels: this.estimateImageDimensions(image), hasTransparency: this.detectTransparency(header) }; } /** * Perform basic image analysis * @param {Object} image - Image object * @returns {Object} Basic analysis results */ performBasicAnalysis(image) { const filename = image.name.toLowerCase(); const base64Data = image.base64; // Analyze filename for content hints const filenameHints = this.analyzeFilename(filename); // Estimate color information from compression ratio const colorAnalysis = this.estimateColors(image); // Detect potential content type const contentType = this.detectContentType(filename, image); return { filenameHints, colorAnalysis, contentType, quality: this.assessImageQuality(image) }; } /** * Generate content description based on available data * @param {Object} image - Image object * @returns {Object} Content analysis */ generateContentDescription(image) { const filename = image.name.toLowerCase(); const size = image.originalSize; let description = 'Image content analysis based on metadata:'; let suggestedContent = []; let detectedElements = []; // Analyze filename for content clues if (filename.includes('logo') || filename.includes('brand')) { suggestedContent.push('Logo or branding element'); detectedElements.push('Brand identity', 'Text elements', 'Geometric shapes'); } if (filename.includes('screenshot') || filename.includes('screen')) { suggestedContent.push('Screenshot or screen capture'); detectedElements.push('UI elements', 'Text content', 'Interface components'); } if (filename.includes('photo') || filename.includes('img') || filename.includes('pic')) { suggestedContent.push('Photograph or image'); detectedElements.push('Natural elements', 'People or objects', 'Realistic content'); } if (filename.includes('chart') || filename.includes('graph') || filename.includes('data')) { suggestedContent.push('Chart, graph, or data visualization'); detectedElements.push('Data points', 'Axes', 'Labels', 'Statistical information'); } if (filename.includes('icon') || filename.includes('button')) { suggestedContent.push('Icon or button element'); detectedElements.push('Simple graphics', 'Symbolic representation'); } // Analyze size for content hints if (size > 500000) { // > 500KB detectedElements.push('High detail content', 'Complex imagery'); } else if (size < 50000) { // < 50KB detectedElements.push('Simple graphics', 'Low complexity'); } // Vietnamese filename analysis if (this.containsVietnamese(filename)) { suggestedContent.push('Vietnamese content detected'); detectedElements.push('Vietnamese text', 'Local content'); } return { description, suggestedContent: suggestedContent.length > 0 ? suggestedContent : ['General image content'], detectedElements: detectedElements.length > 0 ? detectedElements : ['Unknown elements'], confidence: this.calculateConfidence(suggestedContent.length, detectedElements.length), limitations: [ 'Analysis based on metadata and filename only', 'Visual content analysis requires direct image processing', 'Color and object detection limited without pixel data access' ] }; } /** * Analyze filename for content hints * @param {string} filename - Image filename * @returns {Object} Filename analysis */ analyzeFilename(filename) { const hints = { language: this.detectLanguage(filename), contentType: this.guessContentFromFilename(filename), keywords: this.extractKeywords(filename) }; return hints; } /** * Estimate colors from compression data * @param {Object} image - Image object * @returns {Object} Color analysis */ estimateColors(image) { const compressionRatio = parseFloat(image.compressionRatio) || 0; let colorComplexity = 'medium'; let estimatedPalette = []; if (compressionRatio > 80) { colorComplexity = 'low'; estimatedPalette = ['Limited color palette', 'Possibly monochrome or simple graphics']; } else if (compressionRatio > 50) { colorComplexity = 'medium'; estimatedPalette = ['Moderate color range', 'Mixed content']; } else { colorComplexity = 'high'; estimatedPalette = ['Rich color palette', 'Complex imagery', 'Photographic content']; } return { complexity: colorComplexity, estimatedPalette, compressionEfficiency: compressionRatio > 70 ? 'high' : compressionRatio > 30 ? 'medium' : 'low' }; } /** * Detect content type from filename and metadata * @param {string} filename - Image filename * @param {Object} image - Image object * @returns {string} Detected content type */ detectContentType(filename, image) { const name = filename.toLowerCase(); if (name.includes('logo') || name.includes('brand')) return 'logo'; if (name.includes('screenshot') || name.includes('screen')) return 'screenshot'; if (name.includes('photo') || name.includes('img')) return 'photograph'; if (name.includes('chart') || name.includes('graph')) return 'chart'; if (name.includes('icon') || name.includes('button')) return 'icon'; if (name.includes('banner') || name.includes('header')) return 'banner'; if (name.includes('avatar') || name.includes('profile')) return 'avatar'; return 'general'; } /** * Assess image quality based on compression * @param {Object} image - Image object * @returns {string} Quality assessment */ assessImageQuality(image) { const compressionRatio = parseFloat(image.compressionRatio) || 0; const originalSize = image.originalSize || 0; if (originalSize > 1000000 && compressionRatio < 30) { return 'high'; } else if (originalSize > 100000 && compressionRatio < 60) { return 'medium'; } else { return 'compressed'; } } /** * Detect language from filename * @param {string} filename - Image filename * @returns {string} Detected language */ detectLanguage(filename) { if (this.containsVietnamese(filename)) { return 'vietnamese'; } if (/[a-zA-Z]/.test(filename)) { return 'english'; } return 'unknown'; } /** * Check if text contains Vietnamese characters * @param {string} text - Text to check * @returns {boolean} True if contains Vietnamese */ containsVietnamese(text) { const vietnameseRegex = /[àáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ]/i; return vietnameseRegex.test(text); } /** * Guess content type from filename * @param {string} filename - Image filename * @returns {string} Guessed content type */ guessContentFromFilename(filename) { const contentKeywords = { 'ui': ['button', 'menu', 'interface', 'ui', 'gui'], 'gaming': ['game', 'player', 'character', 'level'], 'business': ['chart', 'report', 'data', 'analytics'], 'social': ['profile', 'avatar', 'social', 'post'], 'web': ['website', 'web', 'page', 'site'], 'mobile': ['mobile', 'app', 'phone', 'android', 'ios'] }; for (const [category, keywords] of Object.entries(contentKeywords)) { if (keywords.some(keyword => filename.includes(keyword))) { return category; } } return 'general'; } /** * Extract keywords from filename * @param {string} filename - Image filename * @returns {Array} Extracted keywords */ extractKeywords(filename) { // Remove file extension and split by common separators const nameWithoutExt = filename.replace(/\.[^/.]+$/, ''); const keywords = nameWithoutExt.split(/[\s\-_\.]+/).filter(word => word.length > 2); return keywords; } /** * Estimate image dimensions from base64 size * @param {Object} image - Image object * @returns {Object} Estimated dimensions */ estimateImageDimensions(image) { const base64Size = image.base64.length; const estimatedBytes = (base64Size * 3) / 4; // Rough estimation based on typical compression const estimatedPixels = Math.sqrt(estimatedBytes / 3); // Assuming 3 bytes per pixel return { estimatedPixels: Math.round(estimatedPixels), estimatedDimensions: `~${Math.round(estimatedPixels)}x${Math.round(estimatedPixels)} (estimated)` }; } /** * Detect transparency from base64 header * @param {string} header - Base64 header * @returns {boolean} True if transparency detected */ detectTransparency(header) { // PNG files typically support transparency return header.includes('data:image/png'); } /** * Calculate confidence score for analysis * @param {number} contentClues - Number of content clues found * @param {number} elementClues - Number of element clues found * @returns {string} Confidence level */ calculateConfidence(contentClues, elementClues) { const totalClues = contentClues + elementClues; if (totalClues >= 4) return 'high'; if (totalClues >= 2) return 'medium'; return 'low'; } /** * Generate summary report for all analyzed images * @param {Array} analyzedImages - Array of analyzed images * @returns {Object} Summary report */ generateSummaryReport(analyzedImages) { if (!analyzedImages || analyzedImages.length === 0) { return { totalImages: 0, summary: 'No images to analyze' }; } const totalSize = analyzedImages.reduce((sum, img) => sum + (img.originalSize || 0), 0); const totalCompressedSize = analyzedImages.reduce((sum, img) => sum + (img.compressedSize || 0), 0); const avgCompressionRatio = analyzedImages.reduce((sum, img) => sum + parseFloat(img.compressionRatio || 0), 0) / analyzedImages.length; const contentTypes = analyzedImages.map(img => img.analysis?.contentType || 'unknown'); const uniqueContentTypes = [...new Set(contentTypes)]; const languages = analyzedImages.map(img => img.analysis?.filenameHints?.language || 'unknown'); const uniqueLanguages = [...new Set(languages)]; return { totalImages: analyzedImages.length, totalOriginalSize: totalSize, totalCompressedSize: totalCompressedSize, averageCompressionRatio: avgCompressionRatio.toFixed(1) + '%', contentTypes: uniqueContentTypes, languages: uniqueLanguages, summary: `Analyzed ${analyzedImages.length} image(s) with ${uniqueContentTypes.length} different content type(s)`, analysisLimitations: [ 'Analysis based on metadata, filename, and compression data only', 'Visual content analysis requires direct pixel access', 'Color and object detection limited without image processing libraries' ] }; } } module.exports = ImageAnalyzer;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zivhdinfo/interactive-feedback-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server