sambit
sambit

Reputation: 133

how to draw contour around text image

How can i generate outline effect on text image in java script?

The closest solution on StackOverflow I found is How to add stroke/outline to transparent PNG image in JavaScript canvas

I have tried this code but it is working fine in any shapeenter image description here But my requirement is to draw outline like below images for example enter image description here

// canvas related variables
var canvas = document.getElementById("canvas");
// canvas.width = 600;
// canvas.height = 400;
var ctx = canvas.getContext("2d");

// variables used in pixel manipulation
var canvases = [];
var imageData, data, imageData1, data1;

// size of sticker outline
var strokeWeight = 8;

// true/false function used by the edge detection method
var defineNonTransparent = function(x, y) {
  return (data1[(y * cw + x) * 4 + 3] > 0);
}
var img = new Image();
document.querySelector('#fileinput').addEventListener('change', function() {
  var file = this.files[0];
  var reader = new FileReader();
  reader.onload = function(event) {
    let innerImageURL = event.target.result;
    console.log(innerImageURL);
    img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = start;
    img.src = innerImageURL;
    // img.src = "https://cdn.glitch.com/c3106e6c-98cb-40e4-b0c1-85257680d25a%2Fsun.png?v=1564472507237";

  };
  reader.readAsDataURL(file);
});

// the image receiving the sticker effect
// var img = new Image();
// img.crossOrigin = "anonymous";
// img.onload = start;
// img.src = "https://cdn.glitch.com/c3106e6c-98cb-40e4-b0c1-85257680d25a%2Fsun.png?v=1564472507237";

function start() {
  console.log(img.width)
  console.log(img.height)
  // resize the main canvas to the image size
  canvas.width = cw = img.width;
  canvas.height = ch = img.height;

  // draw the image on the main canvas
  ctx.drawImage(img, 0, 0);

  // Move every discrete element from the main canvas to a separate canvas
  // The sticker effect is applied individually to each discrete element and
  // is done on a separate canvas for each discrete element
  while (moveDiscreteElementToNewCanvas()) {}

  // add the sticker effect to all discrete elements (each canvas)
  for (var i = 0; i < canvases.length; i++) {
    addStickerEffect(canvases[i], strokeWeight);
    ctx.drawImage(canvases[i], 0, 0);
  }

  // redraw the original image
  //   (necessary because the sticker effect 
  //    slightly intrudes on the discrete elements)
  ctx.drawImage(img, 0, 0);

}

// 
function addStickerEffect(canvas, strokeWeight) {
  var url = canvas.toDataURL();
  var ctx1 = canvas.getContext("2d");
  var pts = canvas.outlinePoints;
  addStickerLayer(ctx1, pts, strokeWeight);
  var imgx = new Image();
  imgx.onload = function() {
    ctx1.drawImage(imgx, 0, 0);
  }
  imgx.src = url;
}


function addStickerLayer(context, points, weight) {

  imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  data1 = imageData.data;

  var points = geom.contour(defineNonTransparent);

  defineGeomPath(context, points)
  context.lineJoin = "round";
  context.lineCap = "round";
  context.strokeStyle = "white";
  context.lineWidth = weight;
  context.stroke();
}

// This function finds discrete elements on the image
// (discrete elements == a group of pixels not touching
//  another groups of pixels--e.g. each individual sprite on
//  a spritesheet is a discreet element)
function moveDiscreteElementToNewCanvas() {

  // get the imageData of the main canvas
  imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  data1 = imageData.data;

  // test & return if the main canvas is empty
  // Note: do this b/ geom.contour will fatal-error if canvas is empty
  var hit = false;
  for (var i = 0; i < data1.length; i += 4) {
    if (data1[i + 3] > 0) {
      hit = true;
      break;
    }
  }
  if (!hit) {
    return;
  }

  // get the point-path that outlines a discrete element
  var points = geom.contour(defineNonTransparent);

  // create a new canvas and append it to page
  var newCanvas = document.createElement('canvas');
  newCanvas.width = canvas.width;
  newCanvas.height = canvas.height;
  // newCanvas.style.display = "none";
  document.body.appendChild(newCanvas);
  canvases.push(newCanvas);
  var newCtx = newCanvas.getContext('2d');

  // attach the outline points to the new canvas (needed later)
  newCanvas.outlinePoints = points;

  // draw just that element to the new canvas
  defineGeomPath(newCtx, points);
  newCtx.save();
  newCtx.clip();
  newCtx.drawImage(canvas, 0, 0);
  newCtx.restore();

  // remove the element from the main canvas
  defineGeomPath(ctx, points);
  ctx.save();
  ctx.clip();
  ctx.globalCompositeOperation = "destination-out";
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.restore();

  return (true);
}


// utility function
// Defines a path on the canvas without stroking or filling that path
function defineGeomPath(context, points) {
  context.beginPath();
  context.moveTo(points[0][0], points[0][1]);
  for (var i = 1; i < points.length; i++) {
    context.lineTo(points[i][0], points[i][1]);
  }
  context.lineTo(points[0][0], points[0][1]);
  context.closePath();
}

////////////////////////////
// Edge Detection
///////////////////////////
(function() {
  geom = {};
  geom.contour = function(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;
  };

  // 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;
      }
    }
  }

})();
#canvas {
  background: #f0f0f0;
  border-radius: 5px;
}

body {
  background-color: #333;
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: Arial, Helvetica, sans-serif;
  min-height: 100vh;
  margin: 0;
}

* {
  box-sizing: border-box;
}

#source {
  display: none;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=<device-width>, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
  <link rel='stylesheet' href='https://code.getmdl.io/1.3.0/material.indigo-pink.min.css'>
</head>

<body>

  <div class='mdl-grid'>
    <div class='mdl-cell mdl-cell--4-col'>
      <label id='buttonUpload' for='fileinput' class='mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent'>
            <input type='file' id='fileinput' style='display: none'>
            Upload
        </label>
      <div class='mdl-tooltip' for='buttonUpload'>
        Upload the image to put inside the marker
      </div>
    </div>
  </div>
  <!-- <h4>Original Image</h4> -->
  <!-- <img height="200px" width="200px" src="https://cdn.glitch.com/c3106e6c-98cb-40e4-b0c1-85257680d25a%2Fsun.png?v=1564472507237"> -->
  <h4>Canvas with sticker effect applied</h4>
  <div class="container">
    <canvas id="canvas" width="600" height="400"></canvas><br>
  </div>

  <h4>Each discrete element of the image is processed on a separate canvas<br>Temp-canvases are shown below for illustration purposes only.</h4>
</body>

</html>

Upvotes: 1

Views: 1723

Answers (1)

Helder Sepulveda
Helder Sepulveda

Reputation: 17644

I was thinking about how to get something close to what you show in your image:

Maybe if we draw the image offset at different angles we can get something close to that:

var s = 16, x = 25, y = 25;
var ctx = document.getElementById('canvas1').getContext('2d')
var img = new Image;
img.onload = draw1;
img.src = "https://imgur.com/download/oXfw9nD/";

function draw1() {
    for (i = 0; i < 360; i++)
        ctx.drawImage(img, x + Math.sin(i) * s, y + Math.cos(i) * s);

    ctx.filter = 'invert(100)'
    ctx.drawImage(img, x, y);
}
<canvas id=canvas1 width=460 height=160></canvas>


You can also add some blur to soften the edges, take a look:

var s = 6, x = 25, y = 25;
var ctx = document.getElementById('canvas1').getContext('2d')
var img = new Image;
img.onload = draw1;
img.src = "https://imgur.com/download/oXfw9nD/";

function draw1() {
    ctx.filter = 'blur(5px)'
    for (i = 0; i < 360; i++)
        ctx.drawImage(img, x + Math.sin(i) * s, y + Math.cos(i) * s);
    ctx.globalCompositeOperation = "source-in";

    ctx.filter = 'invert(100)'
    ctx.globalCompositeOperation = "source-over";
    ctx.drawImage(img, x, y);
}
<canvas id=canvas1 width=460 height=160></canvas>

That should get you closer to what you need

Upvotes: 2

Related Questions