javascriptcsssvgcropclip

Cropping a masked clipped image


I have created an SVG mask that allows showing only a certain part of an image. Here is the code for that:

.svg-defs {
  position: absolute;
  width: 0;
  height: 0;
}

.container {
  position: relative;
  top: 20px;
  left: 50px;
}

.background::before {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: url(https://fastly.picsum.photos/id/553/200/300.jpg?hmac=-A3VLW_dBmwUaXOe7bHhCt-lnmROrPFyTLslwNHVH1A) no-repeat;
  opacity: .1;
}
   
img {
  clip-path: url(#clipper);
}
<svg class='svg-defs'>
  <defs>
    <clipPath id='clipper'>
      <rect x="30" y="120" width="100" height="100" />
    </clipPath>
  </defs>
</svg>
    
<div class='container'>
  <div class='background'>
    <img src="https://fastly.picsum.photos/id/553/200/300.jpg?hmac=-A3VLW_dBmwUaXOe7bHhCt-lnmROrPFyTLslwNHVH1A" />
  </div>
</div>

Please note that I am using .background::before in CSS for the sole purpose of displaying the image as a faded background, for aesthetics.

Now, so far, everything is great. What I want to do is write JavaScript code, which when executed (maybe triggered via a click of a button), it will crop the image to the clipped part only (or the bright part in the image above), and which I should be able to save into a file eventually (in other words, I only want to save the bright section of the image as a new image, or at least be able to display it in a separate img tag)

Thank you.


Solution

  • After a lot of investigation, and reading the article suggested by @BretDonald, I came up with the following solution. I am putting it here for the greater good.

    const cropSize = {
        width: 100,
      height: 100
    }
    
    // Function to crop the image
    const crop = () => {
        const canvas = document.querySelector("#cropped");
      const ctx = canvas.getContext('2d');
      
      ctx.drawImage(img, 70, 140, cropSize.width, cropSize.height, 0, 0, cropSize.width, cropSize.height);
    }
    
    
    const img = new Image();
    img.src = 'https://fastly.picsum.photos/id/290/200/300.jpg?hmac=kjRyFwJ6i5kuROjzxcs6QbXbBr8EptbH5AuVxtMxhQ0';
    
    img.onload = (() => {
    
        const canvas = document.querySelector("#image");
      const ctx = canvas.getContext('2d');
      
      ctx.drawImage(img, 70, 140, cropSize.width, cropSize.height, 70, 140, cropSize.width, cropSize.height);
      
    });
    #main {
      display: flex;
      flex-direction: column;
      align-items: center;
      
      box-sizing: border-box;
      
      width: 500px;
      height: 400px;
      
    }
    
    #container {
      flex-grow: 1;
      
      box-sizing: border-box;
      border: 1px solid black;
      background-color: lightgray;
      
      position: relative;
      width: 100%;
      height: 100%;
      
    
      
      
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    
    #image-and-background {
      
      position: relative;
      
      width: 200px;
      height: 300px;
      
      background-color: white;
      
    }
    
    #background::before {
      content: '';
      background: url('https://fastly.picsum.photos/id/290/200/300.jpg?hmac=kjRyFwJ6i5kuROjzxcs6QbXbBr8EptbH5AuVxtMxhQ0') no-repeat;
      opacity: 0.3;
      
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
    <div id='main'>
      <div id='container'>
        <div id='image-and-background'>
          <div id='background'>
            <canvas id='image' width='200' height='300'></canvas>
          </div>
        </div>
      </div>
    </div>
    
    
    <div>
      <p>Click on the "Crop" button below for a cropped image</p>
      <canvas id='cropped' width='100', height='100'></canvas>
      <div id='buttons'>
        <input type="button" id="button1" onclick="crop()" value="Crop" />
      </div>
    </div>