<p>
  The image on the left is normal, but the one on the right 
  is clipped to the edge of the actual image.
</p>
<p>
  Both image containers have 'background-color: pink', but the clipped container doesn't display it because it is clipped to the image.
</p>
<p>
  Try moving your mouse over the images as well, and note the different cursor behaviour of 'cursor:pointer'. The browser knows where the edge is!
</p>
<button id="imgBtn">Load New Image</button>
<input type="text" id="imgUrl" value="https://i.imgur.com/a4kD8NY.png" />
<div id="img-container"></div>
<script>
  const imgContainer = document.getElementById('img-container');
  
  async function load()
  {
    await loadTestImage(
      document.getElementById('imgUrl').value,
      imgContainer, 
      // Don't Clip
      false);
    
    await loadTestImage(
      document.getElementById('imgUrl').value,
      imgContainer, 
      // Clip
      true);
  }
  
  document.getElementById("imgBtn").onclick = async function() {
      imgContainer.replaceChildren();
      load();
  }
  
  setTimeout(() => load(), 100);
</script>
.bordered-img {
  display: inline-block;
  cursor: pointer;
  background-color: pink;
}

#img-container {
  padding: 10px
}
async function loadTestImage(imgUrl, container, withClipping)
{  
    const imgData = await getImageData(imgUrl);
    const img = document.createElement("img");
  
    img.setAttribute('src', imgData.dataUrl);
    img.setAttribute('width', imgData.width);
    img.setAttribute('height', imgData.height);
    img.setAttribute('class', 'bordered-img');
  
    if (withClipping)
    {
     img.setAttribute('style', `clip-path: polygon(${getClipPath(imgData)})`);
    }
    container.append(img);
};

const cachedImageData = {};
const workingCanvas = document.createElement('canvas');

/** 
 * Takes in a URL, and outputs image data, including the bounding points of an image.
 * Returns a structure containing:
 *  - height : Height of the image.
 *  - width  : Width of the image.
 *  - dataUrl: A data URL containing the image.
 *  - boundingPoints: An array of { x, y } for each image edge point.
 */
async function getImageData(imageUrl)
{
    // See if we have cached data for this image; much faster than recalculating it.
    const cachedData = cachedImageData[imageUrl];

    if (cachedData)
    {
        return cachedData;
    }

    // Load the image.
    let loadPromise = new Promise((resolve, reject) => {        
        const imageElement = new Image();
        imageElement.crossOrigin = "anonymous";
        imageElement.onload = ev => {
            resolve(imageElement);
        };
        imageElement.onerror = ev => {
            reject(`Could not load image from ${imageUrl}.`);
        }
        imageElement.src = imageUrl;
    });

    // Wait for the load to finish.
    let loadedImage;
  
    try 
    {
     loadedImage = await loadPromise;
    }
    catch 
    {
      alert("Could not load image from URL.");    
      return {};
    }
  
    // First off, resize the canvas.
    const canvasWidth = workingCanvas.width = loadedImage.width;
    const canvasHeight = workingCanvas.height = loadedImage.height;

    const drawContext = workingCanvas.getContext("2d");

    // Wipe the canvas.
    drawContext.clearRect(0, 0, workingCanvas.width, workingCanvas.height);

    // Draw the image.
    drawContext.drawImage(loadedImage, 0 ,0);

    // Get the raw pixel data that we can analyse.
    const pixelData = drawContext.getImageData(0, 0, workingCanvas.width, workingCanvas.height)
                                 .data;
   
    // This is used by the marching squares algorithm
    // to determine the outline of the non-transparent
    // pixels on the image.    
    const defineNonTransparent = function (x, y)
    {
        var a=pixelData[(y*canvasWidth+x)*4+3];
     
        // This determines the alpha tolerance; I'm extremely intolerant of transparency;
        // any transparency is basically counted as the edge of the image.
        return(a>99);
    }
  
    // Get the edges.
    var points = contour(defineNonTransparent);

    var data = {
        width: canvasWidth,
        height: canvasHeight,
        boundingPoints: points,
        
        // We'll use the dataURL of the canvas as the actual image.
        dataUrl: workingCanvas.toDataURL()
    };
    
    cachedImageData[imageUrl] = data;

    return data;
}

/**
 * Outputs a clipping path for an image, in the format necessary for CSS clipping.
 */
function getClipPath(imgData) 
{
    let clipPathSet = [];

    imgData.boundingPoints.forEach(p => clipPathSet.push(`${p.x}px ${p.y}px`));

    return clipPathSet.join(',');
};


// Marching Squares Edge Detection
// this is a "marching ants" algorithm used to calc the outline path from the d3-plugin for calculating outline paths
// License: https://github.com/d3/d3-plugins/blob/master/LICENSE
//
// Copyright (c) 2012-2015, Michael Bostock
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
// 
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// 
// * The name Michael Bostock may not be used to endorse or promote products
//   derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// lookup tables for marching directions 
var d3_geom_contourDx = [1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN],
    d3_geom_contourDy = [0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN];

function d3_geom_contourStart(grid)
{
    var x = 0,
        y = 0;

    // search for a starting point; begin at origin 
    // and proceed along outward-expanding diagonals 
    while (true) {
        if (grid(x, y)) {
            return [x, y];
        }
        if (x === 0) {
            x = y + 1;
            y = 0;
        } else {
            x = x - 1;
            y = y + 1;
        }
    }
}

function contour(grid, start) {

    var s = start || d3_geom_contourStart(grid), // starting point 
        c = [],    // contour polygon 
        x = s[0],  // current x position 
        y = s[1],  // current y position 
        dx = 0,    // next x direction 
        dy = 0,    // next y direction 
        pdx = NaN, // previous x direction 
        pdy = NaN, // previous y direction 
        i = 0;

    do {
        // determine marching squares index 
        i = 0;
        if (grid(x - 1, y - 1)) i += 1;
        if (grid(x, y - 1)) i += 2;
        if (grid(x - 1, y)) i += 4;
        if (grid(x, y)) i += 8;

        // determine next direction 
        if (i === 6) {
            dx = pdy === -1 ? -1 : 1;
            dy = 0;
        } else if (i === 9) {
            dx = 0;
            dy = pdx === 1 ? -1 : 1;
        } else {
            dx = d3_geom_contourDx[i];
            dy = d3_geom_contourDy[i];
        }

        // update contour polygon 
        if (dx !== pdx && dy !== pdy) {
            c.push({ x, y });
            pdx = dx;
            pdy = dy;
        }

        x += dx;
        y += dy;
    } while (s[0] !== x || s[1] !== y);

    return c;
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.