如何在文字图像周围绘制轮廓

时间:2020-09-12 07:06:41

标签: javascript image d3.js canvas contour

如何在Java脚本中对文本图像生成轮廓效果?

我在StackOverflow上找到的最接近的解决方案是How to add stroke/outline to transparent PNG image in JavaScript canvas

我已经尝试过此代码,但是它可以在任何形状下正常工作enter image description here 但我的要求是绘制例如以下图像的轮廓 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>

1 个答案:

答案 0 :(得分:1)

我正在考虑如何使图片更接近您的显示:

也许如果我们以不同的角度绘制图像偏移量,我们可以得到接近的图像:

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>


您还可以添加一些模糊效果以柔化边缘,看看:

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>

那应该使您更接近所需的东西