Please bookmark this page to avoid losing your image tool!

Astronomical Chart Image Frame

(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,
    frameColor = 'rgba(173, 216, 230, 0.8)', // Light blue for lines/borders
    backgroundColor = 'rgba(0, 0, 20, 1)',   // Very dark blue for background
    labelColor = 'rgba(200, 220, 255, 0.9)', // Lighter blue/white for text
    framePadding = 30,                       // Space between image and inner frame border
    borderThickness = 5,                     // Thickness of the main inner frame border
    outerMargin = 50,                        // Space for ticks and labels outside inner frame
    gridLineColor = 'rgba(100, 100, 150, 0.3)', // Dimmed blue/purple for grid
    showGrid = 1,                            // Number: 1 to show grid, 0 to hide
    fontSize = 12,                           // Font size for labels
    numRaIntervals = 10,                     // Number of intervals for Right Ascension (horizontal)
    numDecIntervals = 8                      // Number of intervals for Declination (vertical)
) {
    // Ensure numerical parameters are numbers and have valid ranges
    numRaIntervals = Math.max(1, Number(numRaIntervals));
    numDecIntervals = Math.max(1, Number(numDecIntervals));
    framePadding = Math.max(0, Number(framePadding));
    borderThickness = Math.max(0, Number(borderThickness));
    outerMargin = Math.max(0, Number(outerMargin));
    fontSize = Math.max(1, Number(fontSize));

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Calculate canvas dimensions
    // Ensure non-negative dimensions even if originalImg is tiny or params are zero
    const imgWidth = originalImg.width || 0;
    const imgHeight = originalImg.height || 0;

    const canvasWidth = imgWidth + 2 * framePadding + 2 * outerMargin;
    const canvasHeight = imgHeight + 2 * framePadding + 2 * outerMargin;
    
    // Prevent 0x0 canvas if all inputs lead to it, ensure at least 1x1
    canvas.width = Math.max(1, canvasWidth);
    canvas.height = Math.max(1, canvasHeight);

    // 1. Background Color
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Coordinate definitions
    const innerFrameX = outerMargin;
    const innerFrameY = outerMargin;
    const innerFrameWidth = imgWidth + 2 * framePadding;
    const innerFrameHeight = imgHeight + 2 * framePadding;

    const imageX = outerMargin + framePadding;
    const imageY = outerMargin + framePadding;

    // 2. Grid Lines (if showGrid is 1)
    if (Number(showGrid) === 1 && innerFrameWidth > 0 && innerFrameHeight > 0) {
        ctx.strokeStyle = gridLineColor;
        ctx.lineWidth = 1;

        // Vertical grid lines
        if (numRaIntervals > 1) { // Only draw if there's more than one interval
            const raGridStep = innerFrameWidth / numRaIntervals;
            for (let i = 1; i < numRaIntervals; i++) {
                const x = innerFrameX + i * raGridStep;
                ctx.beginPath();
                ctx.moveTo(x, innerFrameY);
                ctx.lineTo(x, innerFrameY + innerFrameHeight);
                ctx.stroke();
            }
        }

        // Horizontal grid lines
        if (numDecIntervals > 1) { // Only draw if there's more than one interval
            const decGridStep = innerFrameHeight / numDecIntervals;
            for (let i = 1; i < numDecIntervals; i++) {
                const y = innerFrameY + i * decGridStep;
                ctx.beginPath();
                ctx.moveTo(innerFrameX, y);
                ctx.lineTo(innerFrameX + innerFrameWidth, y);
                ctx.stroke();
            }
        }
    }

    // 3. Draw original image
    if (imgWidth > 0 && imgHeight > 0) {
        ctx.drawImage(originalImg, imageX, imageY, imgWidth, imgHeight);
    }

    // 4. Main Border
    if (borderThickness > 0 && innerFrameWidth > 0 && innerFrameHeight > 0) {
        ctx.strokeStyle = frameColor;
        ctx.lineWidth = borderThickness;
        ctx.strokeRect(innerFrameX, innerFrameY, innerFrameWidth, innerFrameHeight);
    }

    // 5. Ticks and Labels (only if outerMargin allows)
    if (outerMargin > fontSize) { // Basic check if there's space for labels
        ctx.font = `${fontSize}px monospace`;
        ctx.fillStyle = labelColor;
        ctx.strokeStyle = frameColor; // Ticks will use frameColor
        ctx.lineWidth = 1;            // Ticks are thin

        const tickLength = Math.max(1, outerMargin * 0.2);
        const majorTickLength = Math.max(1, outerMargin * 0.35);
        const labelOffset = fontSize * 0.5;

        const isMajorTick = (index, totalIntervals) => {
            if (index === 0 || index === totalIntervals) return true;
            if (totalIntervals % 2 === 0 && index === totalIntervals / 2) return true;
            return false;
        };

        // Top Ticks & Labels (RA-like)
        if (innerFrameWidth >= 0) { // Check if frame has non-negative width
            const raTickStep = (numRaIntervals > 0 && innerFrameWidth > 0) ? innerFrameWidth / numRaIntervals : 0;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';
            for (let i = 0; i <= numRaIntervals; i++) {
                const x = innerFrameX + i * raTickStep;
                const currentTickLength = isMajorTick(i, numRaIntervals) ? majorTickLength : tickLength;
                ctx.beginPath();
                ctx.moveTo(x, innerFrameY);
                ctx.lineTo(x, innerFrameY - currentTickLength);
                ctx.stroke();

                const raValue = (numRaIntervals > 0) ? (i * (24 / numRaIntervals)) : 0;
                const raLabel = (raValue % 1 === 0) ? raValue.toString() : raValue.toFixed(1);
                ctx.fillText(`${raLabel}h`, x, innerFrameY - currentTickLength - labelOffset);
            }
        }

        // Bottom Ticks & Labels (RA-like)
        if (innerFrameWidth >= 0) {
            const raTickStep = (numRaIntervals > 0 && innerFrameWidth > 0) ? innerFrameWidth / numRaIntervals : 0;
            ctx.textBaseline = 'top';
            for (let i = 0; i <= numRaIntervals; i++) {
                const x = innerFrameX + i * raTickStep;
                const currentTickLength = isMajorTick(i, numRaIntervals) ? majorTickLength : tickLength;
                ctx.beginPath();
                ctx.moveTo(x, innerFrameY + innerFrameHeight);
                ctx.lineTo(x, innerFrameY + innerFrameHeight + currentTickLength);
                ctx.stroke();
                
                const raValue = (numRaIntervals > 0) ? (i * (24 / numRaIntervals)) : 0;
                const raLabel = (raValue % 1 === 0) ? raValue.toString() : raValue.toFixed(1);
                ctx.fillText(`${raLabel}h`, x, innerFrameY + innerFrameHeight + currentTickLength + labelOffset);
            }
        }


        // Left Ticks & Labels (DEC-like)
        if (innerFrameHeight >= 0) {
            const decTickStep = (numDecIntervals > 0 && innerFrameHeight > 0) ? innerFrameHeight / numDecIntervals : 0;
            ctx.textAlign = 'right';
            ctx.textBaseline = 'middle';
            for (let i = 0; i <= numDecIntervals; i++) {
                const y = innerFrameY + i * decTickStep;
                const currentTickLength = isMajorTick(i, numDecIntervals) ? majorTickLength : tickLength;
                ctx.beginPath();
                ctx.moveTo(innerFrameX, y);
                ctx.lineTo(innerFrameX - currentTickLength, y);
                ctx.stroke();

                let decValue = (numDecIntervals > 0) ? (90 - (i * (180 / numDecIntervals))) : 0;
                decValue = Math.round(decValue);
                const decLabel = (decValue >= 0 ? '+' : '') + decValue + '°'; // Show + for 0 too for consistency
                if (decValue === 0) decLabel = '0°'; // No sign for 0
                ctx.fillText(decLabel, innerFrameX - currentTickLength - labelOffset, y);
            }
        }

        // Right Ticks & Labels (DEC-like)
        if (innerFrameHeight >= 0) {
            const decTickStep = (numDecIntervals > 0 && innerFrameHeight > 0) ? innerFrameHeight / numDecIntervals : 0;
            ctx.textAlign = 'left';
            // ctx.textBaseline = 'middle'; // already set
            for (let i = 0; i <= numDecIntervals; i++) {
                const y = innerFrameY + i * decTickStep;
                const currentTickLength = isMajorTick(i, numDecIntervals) ? majorTickLength : tickLength;
                ctx.beginPath();
                ctx.moveTo(innerFrameX + innerFrameWidth, y);
                ctx.lineTo(innerFrameX + innerFrameWidth + currentTickLength, y);
                ctx.stroke();

                let decValue = (numDecIntervals > 0) ? (90 - (i * (180 / numDecIntervals))) : 0;
                decValue = Math.round(decValue);
                const decLabel = (decValue >= 0 ? '+' : '') + decValue + '°';
                if (decValue === 0) decLabel = '0°';
                ctx.fillText(decLabel, innerFrameX + innerFrameWidth + currentTickLength + labelOffset, y);
            }
        }
    }
    
    return canvas;
}

Free Image Tool Creator

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

Description

The Astronomical Chart Image Frame tool enhances astronomical images by adding customizable frames, grid lines, and labels. Users can specify frame colors, background colors, and settings for label appearance, grid visibility, and tick intervals for Right Ascension and Declination. This tool is useful for astronomers and educators looking to create visually informative representations of celestial images, allowing for easier interpretation and presentation of astronomical data.

Leave a Reply

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