Please bookmark this page to avoid losing your image tool!

Image Candy Wrapper Filter Effect Tool

(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, twistStr = "30", bulgeStr = "0.15", radiusFactorStr = "0.75", centerXFactorStr = "0.5", centerYFactorStr = "0.5") {
    const twistAngleDegrees = parseFloat(twistStr);
    const bulgeAmount = parseFloat(bulgeStr); // Positive for bloat/bulge, negative for pinch/pucker
    const radiusFactor = parseFloat(radiusFactorStr);
    const centerXFactor = parseFloat(centerXFactorStr);
    const centerYFactor = parseFloat(centerYFactorStr);

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

    if (width === 0 || height === 0) {
        canvas.width = 0;
        canvas.height = 0;
        return canvas;
    }
    
    canvas.width = width;
    canvas.height = height;

    // Draw the original image to a source canvas to get its pixel data
    const srcCanvas = document.createElement('canvas');
    srcCanvas.width = width;
    srcCanvas.height = height;
    const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true });
    srcCtx.drawImage(originalImg, 0, 0, width, height);
    const srcImageData = srcCtx.getImageData(0, 0, width, height);
    const srcData = srcImageData.data;

    const destImageData = ctx.createImageData(width, height);
    const destData = destImageData.data;

    const actualCenterX = width * centerXFactor;
    const actualCenterY = height * centerYFactor;
    const actualEffectRadius = Math.min(width, height) / 2 * radiusFactor;
    
    const twistAngleRad = twistAngleDegrees * Math.PI / 180;

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const dx = x - actualCenterX; // Delta x from center
            const dy = y - actualCenterY; // Delta y from center
            const dist = Math.sqrt(dx * dx + dy * dy); // Distance from center
            const angle = Math.atan2(dy, dx); // Angle from center

            let srcX, srcY;

            if (dist < actualEffectRadius && actualEffectRadius > 0) {
                let normalized_dist = dist / actualEffectRadius; // Normalized distance [0, 1)

                // 1. Inverse Bulge/Pinch transformation
                // Calculates the source radius before bulge/pinch was applied
                let radius_after_unbulge_normalized;
                const power = 1.0 - bulgeAmount;

                if (normalized_dist === 0) { // Pixel is exactly at the center
                    radius_after_unbulge_normalized = 0;
                } else if (Math.abs(power) < 0.00001) { // bulgeAmount is very close to 1.0 (power is ~0)
                    // For bulge ~1 (max bloat), source points are pulled from the center
                    radius_after_unbulge_normalized = (normalized_dist < 1.0) ? 0.0 : 1.0; // Effectively 0 for interior points
                } else {
                    const unBulgePower = 1.0 / power;
                    radius_after_unbulge_normalized = Math.pow(normalized_dist, unBulgePower);
                }
                
                // Safety check for results from Math.pow if extreme values were used
                if (isNaN(radius_after_unbulge_normalized) || !isFinite(radius_after_unbulge_normalized)) {
                    // Fallback: if normalized_dist was 0, result is 0. Otherwise, could be from pow(negative, non-integer) or pow(0, negative)
                    // Default to a state that implies minimal distortion or center mapping
                     radius_after_unbulge_normalized = (normalized_dist === 0 || normalized_dist < 1.0 && bulgeAmount >=1.0) ? 0.0 : normalized_dist; 
                }

                const radius_after_unbulge = radius_after_unbulge_normalized * actualEffectRadius;

                // 2. Inverse Twist transformation
                // Applied to the coordinates obtained after un-bulging/un-pinching
                const dist_for_swirl = radius_after_unbulge; 
                let normalized_dist_for_swirl = (actualEffectRadius === 0) ? 0 : dist_for_swirl / actualEffectRadius;
                
                // Clamp normalized_dist_for_swirl to [0,1] to keep swirl factor sensible
                // This is important if un-bulge produces a radius larger than actualEffectRadius
                normalized_dist_for_swirl = Math.max(0.0, Math.min(1.0, normalized_dist_for_swirl));

                // Standard swirl falloff: effect is strongest at center, zero at radius edge
                const swirlEffectFactor = 1.0 - normalized_dist_for_swirl; 
                // Alternative: swirl strongest at edge: const swirlEffectFactor = normalized_dist_for_swirl;

                const angle_offset = twistAngleRad * swirlEffectFactor;
                const final_src_angle = angle - angle_offset; // Subtract for inverse mapping (destination to source)

                const srcX_relative = dist_for_swirl * Math.cos(final_src_angle);
                const srcY_relative = dist_for_swirl * Math.sin(final_src_angle);

                srcX = actualCenterX + srcX_relative;
                srcY = actualCenterY + srcY_relative;

            } else { // Outside effect radius, or radius is zero
                srcX = x;
                srcY = y;
            }

            const destIndex = (y * width + x) * 4;

            // Final safety check for srcX, srcY before sampling
            if (isNaN(srcX) || isNaN(srcY) || !isFinite(srcX) || !isFinite(srcY)) {
                // Copy original pixel (or set to transparent black) if calculations led to invalid coords
                const origIndex = (y * width + x) * 4;
                destData[destIndex]     = srcData[origIndex];
                destData[destIndex + 1] = srcData[origIndex + 1];
                destData[destIndex + 2] = srcData[origIndex + 2];
                destData[destIndex + 3] = srcData[origIndex + 3];
            } else {
                // Bilinear Interpolation for smoother results
                const clampedSrcX = Math.max(0, Math.min(width - 1, srcX));
                const clampedSrcY = Math.max(0, Math.min(height - 1, srcY));

                const sx_f = Math.floor(clampedSrcX); // Source X floor
                const sy_f = Math.floor(clampedSrcY); // Source Y floor
                
                // Ensure ceiling doesn't exceed image boundaries
                const sx_c = Math.min(width - 1, sx_f + 1); // Source X ceil
                const sy_c = Math.min(height - 1, sy_f + 1); // Source Y ceil

                const u = clampedSrcX - sx_f; // Fractional part for x
                const v = clampedSrcY - sy_f; // Fractional part for y

                for (let i = 0; i < 4; i++) { // R, G, B, A channels
                    const cTL = srcData[(sy_f * width + sx_f) * 4 + i]; // Top-left pixel
                    const cTR = srcData[(sy_f * width + sx_c) * 4 + i]; // Top-right pixel
                    const cBL = srcData[(sy_c * width + sx_f) * 4 + i]; // Bottom-left pixel
                    const cBR = srcData[(sy_c * width + sx_c) * 4 + i]; // Bottom-right pixel

                    // Interpolate top row, then bottom row, then vertically between them
                    const topInterpolation = cTL * (1 - u) + cTR * u;
                    const bottomInterpolation = cBL * (1 - u) + cBR * u;
                    
                    destData[destIndex + i] = Math.round(topInterpolation * (1 - v) + bottomInterpolation * v);
                }
            }
        }
    }

    ctx.putImageData(destImageData, 0, 0);
    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 Candy Wrapper Filter Effect Tool allows users to apply a unique visual distortion effect to images, emulating the appearance of a twisted and bulging candy wrapper. Users can customize the effect through parameters such as twist angle, bulge amount, and the position of the effect center. This tool can be beneficial for graphic designers looking to create eye-catching visuals, for enhancing photographs in creative ways, or for adding stylized effects to artwork and social media posts.

Leave a Reply

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