Please bookmark this page to avoid losing your image tool!

Image Custom-shaped Frame Adder

(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.
async function processImage(originalImg, shape = "rectangle", frameColor = "black", frameWidth = 10, starPoints = 5, starInnerRadiusRatio = 0.5) {
    const canvas = document.createElement('canvas');
    const W = originalImg.width;
    const H = originalImg.height;
    canvas.width = W;
    canvas.height = H;
    const ctx = canvas.getContext('2d');

    // If frameWidth is zero or negative, no frame is applied.
    // Draw the original image onto the canvas and return.
    if (frameWidth <= 0) {
        ctx.drawImage(originalImg, 0, 0, W, H);
        return canvas;
    }
    
    // Helper function to define the shape path.
    // The path represents the centerline for the frame's stroke.
    // The bounding box for this centerline path is defined by _x, _y, _w, _h.
    function defineShapePath() {
        ctx.beginPath();
        
        // availableX, availableY, availableWidth, availableHeight for the shape's centerline
        const _x = frameWidth / 2; 
        const _y = frameWidth / 2; 
        const _w = W - frameWidth; 
        const _h = H - frameWidth; 

        // If the available space for the shape path is non-positive, a frame cannot be drawn meaningfully.
        if (_w <= 0 || _h <= 0) {
            return false; // Indicate path could not be defined
        }

        switch (shape.toLowerCase()) {
            case "circle": {
                const centerX = _x + _w / 2;
                const centerY = _y + _h / 2;
                const radius = Math.min(_w, _h) / 2;
                ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
                break;
            }
            case "ellipse": {
                const centerX = _x + _w / 2;
                const centerY = _y + _h / 2;
                const radiusX = _w / 2;
                const radiusY = _h / 2;
                ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI);
                break;
            }
            case "star": {
                const centerX = _x + _w / 2;
                const centerY = _y + _h / 2;
                const outerRadius = Math.min(_w, _h) / 2;
                // Clamp innerRadiusRatio to prevent degenerate shapes or inversion
                const clampedInnerRadiusRatio = Math.max(0.1, Math.min(1, starInnerRadiusRatio));
                const innerRadius = outerRadius * clampedInnerRadiusRatio;
                
                // Ensure star has at least 3 points
                const numEffectivePoints = Math.max(3, Math.floor(starPoints));
                
                for (let i = 0; i < numEffectivePoints * 2; i++) {
                    // Angle calculations to create points of the star
                    const angle = (Math.PI / numEffectivePoints) * i - (Math.PI / 2); // Start from top
                    const r = (i % 2 === 0) ? outerRadius : innerRadius; // Alternate between outer and inner radius
                    const currentX = centerX + r * Math.cos(angle);
                    const currentY = centerY + r * Math.sin(angle);
                    if (i === 0) {
                        ctx.moveTo(currentX, currentY);
                    } else {
                        ctx.lineTo(currentX, currentY);
                    }
                }
                ctx.closePath(); // Close the path to connect the last point to the first
                break;
            }
            case "heart": {
                // Defines a heart shape using Bezier curves within the bounding box _x, _y, _w, _h
                const midX = _x + _w / 2;         // Center X for symmetry
                const topIndentY = _y + _h * 0.25;  // Y-coordinate of the top indent of the heart
                const topLobeY = _y;              // Y-coordinate for the peak of the lobes (top of bounding box)
                const sideControlY = _y + _h * 0.60;  // Y-coordinate for control points defining the curve of the sides
                const bottomTipY = _y + _h;       // Y-coordinate of the bottom tip of the heart

                // X-coordinates for control points, defining the width and curve of lobes
                const leftLobeControlX = _x + _w * 0.25; 
                const rightLobeControlX = _x + _w * 0.75;
                const leftEdgeControlX = _x; 
                const rightEdgeControlX = _x + _w; 
                
                ctx.moveTo(midX, topIndentY); // Start at the top indent
                // Draw left lobe: from top indent, curve out and down to bottom tip
                ctx.bezierCurveTo(leftLobeControlX, topLobeY, leftEdgeControlX, sideControlY, midX, bottomTipY);
                // Draw right lobe: from bottom tip, curve out and up back to top indent
                ctx.bezierCurveTo(rightEdgeControlX, sideControlY, rightLobeControlX, topLobeY, midX, topIndentY);
                ctx.closePath();
                break;
            }
            case "rectangle":
            default: { // Default to rectangle if shape is unknown or "rectangle"
                ctx.rect(_x, _y, _w, _h);
                break;
            }
        }
        return true; // Path successfully defined
    }

    // Attempt to define the shape path
    const pathDefined = defineShapePath();

    // If path could not be defined (e.g., image too small for frameWidth),
    // draw original image without frame and return.
    if (!pathDefined) {
        console.warn("Shape could not be drawn, possibly due to small image dimensions relative to frameWidth. Drawing original image without frame.");
        ctx.drawImage(originalImg, 0, 0, W, H);
        return canvas;
    }

    // Configure and draw the frame stroke
    ctx.strokeStyle = frameColor;
    ctx.lineWidth = frameWidth;
    ctx.stroke(); // Stroke the defined path

    // Prepare to clip the image to the shape
    ctx.save(); // Save current canvas state
    ctx.clip(); // Clip drawing to the current path. Image will be drawn under the inner half of the stroke.

    // Calculate dimensions to draw the image to "cover" the clipped area, maintaining aspect ratio.
    // Source (originalImg) dimensions
    let sx = 0, sy = 0, sw = originalImg.width, sh = originalImg.height;
    // Destination (canvas) dimensions for drawing the image
    let dx = 0, dy = 0, dw = W, dh = H;

    const imgAspect = originalImg.width / originalImg.height;
    const canvasAspect = W / H; // The overall canvas serves as the target area for the "cover" calculation.

    if (imgAspect > canvasAspect) { // Image is wider than canvas aspect ratio (needs to be cropped left/right)
        dh = H; // Set draw height to canvas height
        dw = H * imgAspect; // Calculate proportional width
        dx = (W - dw) / 2; // Center horizontally
        dy = 0;
    } else { // Image is taller or same aspect ratio (needs to be cropped top/bottom)
        dw = W; // Set draw width to canvas width
        dh = W / imgAspect; // Calculate proportional height
        dx = 0;
        dy = (H - dh) / 2; // Center vertically
    }
    
    // Draw the image, it will be clipped by the path
    ctx.drawImage(originalImg, sx, sy, sw, sh, dx, dy, dw, dh);
    
    ctx.restore(); // Restore canvas state to remove clipping

    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 Image Custom-shaped Frame Adder is a versatile online tool that allows users to add decorative frames to their images in various custom shapes, such as rectangles, circles, stars, hearts, and ellipses. Users can customize the frame’s color and width to enhance the visual appeal of their images. This tool is ideal for personalizing photos for social media, creating unique greeting cards, or adding a creative touch to digital artwork. It caters to users looking to make their images stand out with custom framing options.

Leave a Reply

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