Reputation: 133
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 shape
But my requirement is to draw outline like below images for example
// 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
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