Please bookmark this page to avoid losing your image tool!

Image Score Calculator

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, contrastWeight = "30", colorWeight = "30", sharpWeight = "40") {
    // Parse weight configurations
    const cW = parseFloat(contrastWeight) || 30;
    const colW = parseFloat(colorWeight) || 30;
    const sW = parseFloat(sharpWeight) || 40;
    const totalWeight = cW + colW + sW;

    // Downscale for fast algorithmic processing while preserving relative characteristics
    const MAX_DIM = 500;
    let scale = Math.min(MAX_DIM / originalImg.width, MAX_DIM / originalImg.height);
    scale = Math.min(scale, 1); // Only downscale, never upscale
    const w = Math.max(3, Math.floor(originalImg.width * scale));
    const h = Math.max(3, Math.floor(originalImg.height * scale));

    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    
    // Draw and extract pixel data
    ctx.drawImage(originalImg, 0, 0, w, h);
    const imgData = ctx.getImageData(0, 0, w, h);
    const data = imgData.data;

    const numPixels = w * h;
    let lumaValues = new Float32Array(numPixels);
    let rgVals = new Float32Array(numPixels);
    let ybVals = new Float32Array(numPixels);
    
    let totalLuma = 0;
    let sumRg = 0;
    let sumYb = 0;

    // First Pass: Calculate Luma, RG, YB channels + Mean values
    let i = 0;
    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {
            const idx = (y * w + x) * 4;
            const r = data[idx];
            const g = data[idx + 1];
            const b = data[idx + 2];

            // Standard relative luminance (sRGB)
            const luma = 0.299 * r + 0.587 * g + 0.114 * b;
            lumaValues[i] = luma;
            totalLuma += luma;

            // Opponent color spaces for Hasler & Süsstrunk's colorfulness metric
            const rg = r - g;
            const yb = 0.5 * (r + g) - b;
            rgVals[i] = rg;
            ybVals[i] = yb;
            sumRg += rg;
            sumYb += yb;

            i++;
        }
    }

    const meanLuma = totalLuma / numPixels;
    const meanRg = sumRg / numPixels;
    const meanYb = sumYb / numPixels;

    // Second Pass: Variances
    let sumLumaVar = 0;
    let sumRgVar = 0;
    let sumYbVar = 0;

    for (let j = 0; j < numPixels; j++) {
        sumLumaVar += (lumaValues[j] - meanLuma) ** 2;
        sumRgVar += (rgVals[j] - meanRg) ** 2;
        sumYbVar += (ybVals[j] - meanYb) ** 2;
    }

    // Standard Deviations
    const stdLuma = Math.sqrt(sumLumaVar / numPixels);
    const stdRg = Math.sqrt(sumRgVar / numPixels);
    const stdYb = Math.sqrt(sumYbVar / numPixels);

    // Colorfulness Metric
    const colorfulness = Math.sqrt(stdRg ** 2 + stdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);

    // Calculate Sharpness (Laplacian Variance)
    let sumLap = 0;
    let lapIndex = 0;
    let lapValues = new Float32Array((w - 2) * (h - 2));

    for (let y = 1; y < h - 1; y++) {
        for (let x = 1; x < w - 1; x++) {
            const top = lumaValues[(y - 1) * w + x];
            const bottom = lumaValues[(y + 1) * w + x];
            const left = lumaValues[y * w + x - 1];
            const right = lumaValues[y * w + x + 1];
            const center = lumaValues[y * w + x];

            // 3x3 Laplacian filter kernel response
            const lap = top + bottom + left + right - 4 * center;
            lapValues[lapIndex] = lap;
            sumLap += lap;
            lapIndex++;
        }
    }

    const meanLap = sumLap / lapIndex;
    let sumLapVar = 0;
    for (let j = 0; j < lapIndex; j++) {
        sumLapVar += (lapValues[j] - meanLap) ** 2;
    }
    const sharpness = lapIndex > 0 ? sumLapVar / lapIndex : 0;

    // Normalize out of 100 for each component (empirical thresholds)
    // Contrast: stddev luminance of ~60 is very high (perfect contrast).
    const contrastNorm = Math.min(100, Math.max(0, (stdLuma / 60) * 100));

    // Colorfulness: ~80+ is highly vivid/colorful.
    const colorNorm = Math.min(100, Math.max(0, (colorfulness / 80) * 100));

    // Sharpness: variance > 800 correlates to highly sharp contours/focus.
    const sharpnessNorm = Math.min(100, Math.max(0, (sharpness / 800) * 100));

    // Final Weighted Score (Бал)
    const finalScore = ((contrastNorm * cW) + (colorNorm * colW) + (sharpnessNorm * sW)) / totalWeight;

    // Prepare visual display
    // Get original image data url to show in the UI
    const origCanvas = document.createElement('canvas');
    origCanvas.width = originalImg.width;
    origCanvas.height = originalImg.height;
    origCanvas.getContext('2d').drawImage(originalImg, 0, 0);
    const imgDataUrl = origCanvas.toDataURL("image/jpeg", 0.8);

    // Build SVG Gauge
    const radius = 55;
    const circumference = 2 * Math.PI * radius;
    const offset = circumference - (finalScore / 100) * circumference;
    const gaugeColor = finalScore >= 80 ? '#2ecc71' : finalScore >= 50 ? '#f1c40f' : '#e74c3c';

    const renderBar = (label, score, color) => `
        <div style="margin-bottom: 12px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
            <div style="display: flex; justify-content: space-between; font-size: 13px; font-weight: 600; color: #444; margin-bottom: 6px;">
                <span>${label}</span>
                <span>${Math.round(score)}/100</span>
            </div>
            <div style="background: #dfe6e9; border-radius: 6px; height: 8px; overflow: hidden;">
                <div style="background: ${color}; width: ${score}%; height: 100%; border-radius: 6px; transition: width 1s ease-in-out;"></div>
            </div>
        </div>
    `;

    const container = document.createElement('div');
    container.style.cssText = `
        display: flex; 
        flex-wrap: wrap; 
        gap: 24px; 
        background: #ffffff; 
        padding: 24px; 
        border-radius: 12px; 
        box-shadow: 0 8px 24px rgba(0,0,0,0.08); 
        max-width: 800px;
        margin: 0 auto;
        border: 1px solid #f1f2f6;
    `;

    container.innerHTML = `
        <div style="flex: 1; min-width: 250px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f8f9fa; border-radius: 8px; padding: 10px;">
            <img src="${imgDataUrl}" style="max-width: 100%; max-height: 350px; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" alt="Original Image" />
        </div>
        
        <div style="flex: 1; min-width: 280px; display: flex; flex-direction: column; justify-content: center;">
            <div style="text-align: center; margin-bottom: 20px;">
                <h3 style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0 0 5px 0; color: #2c3e50; font-size: 22px;">Image Score Calculator</h3>
                <p style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; color: #7f8c8d; font-size: 14px; text-transform: uppercase; letter-spacing: 1px;">Загальний Бал</p>
                
                <div style="margin-top: 15px; position: relative; display: inline-block;">
                    <svg width="140" height="140" viewbox="0 0 140 140">
                        <circle cx="70" cy="70" r="${radius}" fill="none" stroke="#f1f2f6" stroke-width="12" />
                        <circle cx="70" cy="70" r="${radius}" fill="none" stroke="${gaugeColor}" stroke-width="12"
                                stroke-dasharray="${circumference}" stroke-dashoffset="${offset}"
                                transform="rotate(-90 70 70)" stroke-linecap="round" />
                    </svg>
                    <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center;">
                        <span style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 32px; font-weight: 800; color: #2d3436;">
                            ${Math.round(finalScore)}
                        </span>
                    </div>
                </div>
            </div>

            <div style="padding: 15px; background: #f8f9fa; border-radius: 8px;">
                ${renderBar('Sharpness (Чіткість)', sharpnessNorm, '#3498db')}
                ${renderBar('Contrast (Контраст)', contrastNorm, '#9b59b6')}
                ${renderBar('Colorfulness (Насиченість)', colorNorm, '#e67e22')}
            </div>
        </div>
    `;

    return container;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Score Calculator is an analytical tool designed to evaluate the visual quality of an image based on three key technical metrics: sharpness, contrast, and colorfulness. By processing pixel data through mathematical algorithms, the tool provides a weighted overall score and individual breakdown bars to help users understand the aesthetic and technical composition of their photos. This tool is particularly useful for photographers, content creators, and digital artists looking to objectively assess image clarity, color vibrancy, and tonal range to ensure their visuals meet specific quality standards.

Leave a Reply

Your email address will not be published. Required fields are marked *