how to draw contour around text image - javascript

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>

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

Related

Diagonal lines on canvas are drawing with different color/opacity

I'm drawing diagonal lines on an HTML canvas with a specific size, but some of them appear to have different color/opacity than the others. I would like them to have the same color/opacity.
Diagonal lines picture
The code I'm using to generate this output is the following:
let artist = {
step: 50,
distanceBetweenLines: 10,
verticalDifferenceInLines: 150,
}
window.onload = function () {
canv = document.getElementById("gc");
ctx = canv.getContext("2d");
ctx.translate(0.5, 0.5);
ctx.lineWidth = "1";
ctx.strokeStyle = "black";
draw();
}
function draw () {
let step = artist.step;
let d = artist.distanceBetweenLines;
let v = artist.verticalDifferenceInLines;
for (let x = 0; x < 500; x += step) {
for (let y = 0; y < 500; y += step) {
let increment = 30;
line({x:x, y: y}, {x:x+increment, y: y+increment});
}
}
}
function line(init, end) {
ctx.beginPath();
ctx.moveTo(init.x, init.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
}
Why I'm getting this effect on some of the lines?
This is a chrome bug. I opened an issue here.
That's basically a problem with Hardware Acceleration, if you disable it, it will render nicely even in Chrome.
To workaround the issue, you can compose a single bigger path which will contain all your lines and call stroke() only once:
let artist = {
step: 50,
distanceBetweenLines: 10,
verticalDifferenceInLines: 150,
}
window.onload = function() {
canv = document.getElementById("gc");
ctx = canv.getContext("2d");
ctx.translate(0.5, 0.5);
ctx.lineWidth = "1";
ctx.strokeStyle = "black";
draw();
}
function draw() {
let step = artist.step;
let d = artist.distanceBetweenLines;
let v = artist.verticalDifferenceInLines;
// a single big path
ctx.beginPath();
for (let x = 0; x < 500; x += step) {
for (let y = 0; y < 500; y += step) {
let increment = 30;
line({
x: x,
y: y
}, {
x: x + increment,
y: y + increment
});
}
}
// stroke only once
ctx.stroke();
}
function line(init, end) {
ctx.moveTo(init.x, init.y);
ctx.lineTo(end.x, end.y);
}
<canvas id="gc" width="500" height="500"></canvas>
But for this exact drawing, it would even be better to use a CanvasPattern:
// An helper function to create CanvasPatterns
// returns a 2DContext on which a simple `finalize` method is attached
// method which does return a CanvasPattern from the underlying canvas
function patternMaker( width, height ) {
const canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext( '2d' );
ctx.finalize = (repetition = "repeat") => ctx.createPattern( canvas, repetition );
return ctx;
}
const canvas = document.getElementById("gc");
const ctx = canvas.getContext("2d");
const step = 50;
const offset = 30;
const patt_maker = patternMaker( step, step );
patt_maker.translate( 0.5, 0.5 );
patt_maker.moveTo( 0, 0 );
patt_maker.lineTo( offset, offset );
patt_maker.stroke();
const patt = patt_maker.finalize();
ctx.fillStyle = patt;
ctx.fillRect( 0, 0, canvas.width, canvas.height );
<canvas id="gc" width="500" height="500"></canvas>

JavaScript canvas.drawImage alpha loss

Pretty new to JavaScript.
I'm building a site that compares the template image with the user's input and builds some image processing directives. The template image is stored in the site's folder.
$(function() {
$("#btnDirective").click(generateEmoteDirectiveFile);
});
function generateEmoteDirectiveFile() {
getImageData('imgs/templates/human.png');
}
function getImageData(source) {
var image = new Image();
image.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var px = 0, cx = canvas.width * canvas.height * 4; px < cx; px += 4)
if (data[px+3] != 255 && data[px+3] != 0)
console.log("Alpha: " + data[px+3]);
};
image.src = source;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="scripts/jquery-1.11.3.min.js"></script>
<script src="scripts/script.js"></script>
</head>
<body>
<button id="btnDirective">Generate</button>
</body>
</html>
'imgs/templates/human.png' contains a lot of semitransparent values, that are never fetched:
So you are trying to get the integer values for each pixel where it's alpha value of neither 0 nor 255?
Well, your image has pixels that are either full transparent, or they are solid white/black. So you will have no partially-transparent pixels.
See the image of the chili pepper emoji from Emojipedia that is used below. Around ~6% of its pixels are partially transparent (around the borders).
$(function() {
$('#btnDirective').click(generateEmoteDirectiveFile);
});
function generateEmoteDirectiveFile() {
getImageData('https://i.imgur.com/4LzuX3G.png');
getImageData('https://i.imgur.com/R7g697w.png'); // Using a testable image
}
function getImageData(source) {
var image = new Image();
image.crossOrigin = 'Anonymous'; // This allows Imgur CORS...
image.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
document.body.appendChild(canvas); // Add canvas to body...
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
var pixels = canvas.width * canvas.height;
var alphaValues = [];
for (var px = 0, cx = pixels * 4; px < cx; px += 4) {
if (data[px + 3] != 255 && data[px + 3] != 0) {
alphaValues.push(data[px + 3]);
}
}
var nonAlphaPixels = (alphaValues.length / pixels * 100).toFixed(2);
console.log('Alpha (' + nonAlphaPixels + '%): ' + alphaValues.join(','));
};
image.src = source;
}
function drawPartialPixels(imageData) {
}
body { background: #444; }
#btnDirective { display: block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnDirective">Generate</button>
Here is an example of drawing only the partial pixels.
$(function() {
$('#btnDirective').click(generateEmoteDirectiveFile);
});
function generateEmoteDirectiveFile() {
getImageData('https://i.imgur.com/R7g697w.png', onImageLoad);
}
function getImageData(source, onLoadFn) {
var image = new Image();
image.src = source;
image.crossOrigin = 'Anonymous'; // This allows Imgur CORS...
image.onload = function() {
onLoadFn(this);
};
}
function onImageLoad(image) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = canvas.width * canvas.height;
for (var px = 0, cx = pixels * 4; px < cx; px += 4) {
var partialAlpha = imageData.data[px + 3] != 255 && imageData.data[px + 3] != 0;
imageData.data[px + 0] = partialAlpha ? 255 : 0;
imageData.data[px + 1] = partialAlpha ? 0 : 0;
imageData.data[px + 2] = partialAlpha ? 0 : 0;
imageData.data[px + 3] = partialAlpha ? 255 : 0;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
document.body.appendChild(canvas); // Add canvas to body...
}
body { background: #444; }
#btnDirective { display: block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnDirective">Generate</button>

Loop Background Image Animation in Canvas

I am new to Canvas and want to loop background Image in the below smoke effect. On searching, I have found an example that how we can loop background Image in canvas Link to looping animation so I tried integrating the looping code with the smoke effect but no success. Any help will be appreciated.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
context.drawImage(backImg, 0, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-lights';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas" ></canvas>
I just added a few lines. Hopefully you can spot them. I commented everything I added.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
// x position of scrolling image
var imageX = 0;
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
// draw twice to cover wrap around
context.drawImage(backImg, imageX, 0, canvasWidth, canvasHeight);
context.drawImage(backImg, imageX + canvasWidth, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-light';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
// incrementally change image position of background to scroll left
imageX -= maxVelocity;
if (imageX < -canvasWidth) {
imageX += canvasWidth;
}
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas"></canvas>
Just to add to the answer given some additional improvements to the code structure.
Use requestAnimationFrame to call render calls.
Don't expose properties of objects if not needed.
Don't use forEach iteration in time critical code. Use for loops.
Use constants where ever possible.
Comments that state the obvious are just noise in the source code making it harder to read. Limit comment to abstracts that may not be obvious to another programmer reading the code.
eg
// If an image is set draw it
if (this.image) {
Really is that comment of any use to anyone. Comments should help not degrade the readability of code.
Also the original code tried to set the global composite operations to soft-lights this is not a know operation. I corrected it to soft-light which can on some machines, be a very slow render operation. It may pay to selected another operation for machines that are slow. This can be done by simply monitoring the render time of particles and switching operation type is too slow.
A quick rewrite of the OP's code.
const particles = [];
const particleCount = 60;
const maxVelocity = 2;
var canvasWidth = innerWidth;
var canvasHeight = innerHeight;
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
var ctx;
const backgroundColor = "rgba(255,255,255, .5)";
const backgroundSpeed = -0.1;
var looping = false;
var totalSeconds = 0;
var lastTime = 0;
var frameTime = (1000 / 30) - (1000 / 120); // one quater frame short to
// allow for timing error
var imageCount = 0;
const backImg = new Image();
const imageObj = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
backImg.onload = imageObj.onload = imageLoad;
function imageLoad(){
imageCount += 1;
if(imageCount === 2){
init();
}
}
function init() {
var canvas = myCanvas;
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext('2d');
for (var i = 0; i < particleCount; i += 1) {
particles.push(new Particle(ctx));
}
lastTime = performance.now();
requestAnimationFrame(mainLoop);
}
function mainLoop(time){
if(time-lastTime > frameTime){
lastTime = time;
update();
draw(time);
}
requestAnimationFrame(mainLoop);
}
const rand = (min, max) => Math.random() * (max - min) + min; // names are best short (short only without ambiguity)
function Particle(ctx) {
var x, y, xVel, yVel, radius, image;
const color = "rgba(0, 255, 255, 1)";
x = rand(0, canvasWidth),
y = rand(borderTop, borderBottom);
xVel = rand(-maxVelocity, maxVelocity);
yVel = rand(-maxVelocity, maxVelocity);
radius = 5;
image = imageObj;
this.draw = function () { ctx.drawImage(image, x - 128, y - 128) }
this.update = function () {
x += xVel;
y += yVel;
if (x >= canvasWidth) {
xVel = -xVel;
x = canvasWidth;
}
else if (x <= 0) {
xVel = -xVel;
x = 0;
}
if (y >= borderBottom) {
yVel = -yVel;
y = borderBottom;
}
else if (y <= borderTop) {
yVel = -yVel;
y = borderTop;
}
}
}
function draw(time) {
var i,x;
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
x = time * backgroundSpeed;
x = ((x % canvasWidth) + canvasWidth) % canvasWidth;
ctx.drawImage(backImg, x, 0, canvasWidth, canvasHeight);
ctx.drawImage(backImg, x - canvasWidth, 0, canvasWidth, canvasHeight);
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.globalAlpha = 0.75;
ctx.globalCompositeOperation = 'soft-light';
for(i = 0; i < particles.length; i += 1){
particles[i].draw();
}
}
function update() {
for(i = 0; i < particles.length; i += 1){
particles[i].update();
}
}
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=myCanvas></canvas>

greyscale canvas - make canvas color to black and white

I am using this js: http://www.jqueryscript.net/other/jQuery-Image-Processing-Plugin-With-Canvas-Tancolor.html
What this plugin is doing, it is converting my canvas to black and white and saving it in an IMG tag on my web page.
Here is the javascript this plugin is using:
(function ($) {
$.fn.tancolor = function(options) {
var settings = $.extend({
mode: 'grayscale',
r_weight: 0.34,
g_weight: 0.5,
b_weight: 0.16,
r_intensity: 1,
g_intensity: 1,
b_intensity: 1,
load: null
}, options );
var r_weight;
var g_weight;
var b_weight;
var r_intensity;
var g_intensity;
var b_intensity;
// settings value
switch(settings.mode){
case 'grayscale':
r_weight = 0.34;
g_weight = 0.5;
b_weight = 0.16;
r_intensity = 1;
g_intensity = 1;
b_intensity = 1;
break;
case 'red':
r_weight = 0.34;
g_weight = 0.5;
b_weight = 0.16;
r_intensity = 255;
g_intensity = 1;
b_intensity = 1;
break;
case 'green':
r_weight = 0.34;
g_weight = 0.5;
b_weight = 0.16;
r_intensity = 1;
g_intensity = 255;
b_intensity = 1;
break;
case 'blue':
r_weight = 0.34;
g_weight = 0.5;
b_weight = 0.16;
r_intensity = 1;
g_intensity = 1;
b_intensity = 255;
break;
default:
r_weight = settings.r_weight;
g_weight = settings.g_weight;
b_weight = settings.b_weight;
r_intensity = settings.r_intensity;
g_intensity = settings.g_intensity;
b_intensity = settings.b_intensity;
break;
}
// convert image to canvas
var img = document.getElementById($(this).attr("id"));
if(settings.load){
img.src = settings.load;
return;
}
var canvas = convertImageToCanvas(img);
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData(0, 0, img.width, img.height)
$(this).replaceWith(canvas);
// Processing image data
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
var brightness = r_weight * data[i] + g_weight * data[i + 1] + b_weight * data[i + 2];
// red
data[i] = r_intensity * brightness;
// green
data[i + 1] = g_intensity * brightness;
// blue
data[i + 2] = b_intensity * brightness;
}
ctx.putImageData(imageData, 0, 0);
$('#'+$(this).attr("id")).each(function(i,e){
var img = e.toDataURL("image/png");
$(e).replaceWith( $('<img src="' + img + '"/>').attr({width: $(e).attr("width"), height: $(e).attr("height"), style: $(e).attr("style") }) ) });
// Converts image to canvas; returns new canvas element
function convertImageToCanvas(image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
if(image.id) {
canvas.id = image.id;
}
if(image.className) {
canvas.className = image.className;
}
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
}
// Converts canvas to an image
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
}
};
}(jQuery));
What I actually want is just convert my canvas to black and white but saving it in canvas tag only on web page and not as IMG tag as I am than after downloading the greyscale canvas merging with another canvas from the app.
Any help will be appreciated.
Thanks
To grayscale a non-transparent image, on compatible browsers, you could simply [if Safari didn't had a strange bug with that, you should have been able to] use the context.globalCompositeOperation property and set it to 'luminosity' after you've drawn a solid color, before falling to the getImageData solution :
var img = new Image();
var ctx = canvas.getContext('2d');
img.onload = function() {
// set the canvas' size
canvas.width = this.width;
canvas.height = this.height;
// first fill a rect
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, canvas.width, canvas.height);
// set the gCO
ctx.globalCompositeOperation = 'luminosity';
// if the browser doesn't support Blend Modes
console.log(ctx.globalCompositeOperation)
if (ctx.globalCompositeOperation !== 'luminosity')
fallback(this);
else {
// draw the image
ctx.drawImage(this, 0, 0);
// reset the gCO
ctx.globalCompositeOperation = 'source-over';
}
}
img.crossOrigin = "anonymous";
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";
function fallback(img) {
// first remove our black rectangle
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw the image
ctx.drawImage(img, 0, 0);
// get the image data
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var d = imgData.data;
// loop through all pixels
// each pixel is decomposed in its 4 rgba values
for (var i = 0; i < d.length; i += 4) {
// get the medium of the 3 first values
var med = (d[i] + d[i + 1] + d[i + 2]) / 3;
// set it to each value
d[i] = d[i + 1] = d[i + 2] = med;
}
// redraw the new computed image
ctx.putImageData(imgData, 0, 0);
}
<canvas id="canvas"></canvas>
But since Safari has a bug and that the gCO solution (which is faster, but I don't think it's important for you) remove the transparent pixels, here is just the previous fallback, which is a simpler code than the one you got. I hope you'll be able to read it.
var img = new Image();
var ctx = canvas.getContext('2d');
img.onload = function() {
// set the canvas' size
canvas.width = this.width;
canvas.height = this.height;
//draw the image
ctx.drawImage(this, 0, 0);
// get the image data
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var d = imgData.data;
// loop through all pixels
// each pixel is decomposed in its 4 rgba values
for (var i = 0; i < d.length; i += 4) {
// get the medium of the 3 first values ( (r+g+b)/3 )
var med = (d[i] + d[i + 1] + d[i + 2]) / 3;
// set it to each value (r = g = b = med)
d[i] = d[i + 1] = d[i + 2] = med;
// we don't touch the alpha
}
// redraw the new computed image
ctx.putImageData(imgData, 0, 0);
}
img.crossOrigin = "anonymous";
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";
<canvas id="canvas"></canvas>

Chroma keying with javascript & jQuery

Okay, we need your help! We (with our informatics class) are building a digital scratchmap! Like this:
(source: megagadgets.nl)
With your mouse you should be able to scratch out the places you've been to. Now we're stuck. We have a canvas and we draw the image of a world map. Then when the user clicks and drags a stroke gets add on top of the world map.
Now we want to convert the (green drawn) strokes to transparency so we can reveal the image behind it. (Just like scratching out the places you've been to and revealing the map behind it (in colour)).
This is our html:
<body>
<h1>Scratchmap</h1>
<hr>
<canvas id="ball" width="600px" height ="600px">
</canvas>
<canvas id="ball2" width="600px" height ="600px">
</canvas>
</body>
And this is our javascript:
// Set variables
var a_canvas = document.getElementById("ball");
var context = a_canvas.getContext("2d");
var a_canvas2 = document.getElementById("ball2");
var context2 = a_canvas2.getContext("2d");
var img = new Image();
img.onload = function () {
context.drawImage(img, img_x, img_y);
}
img.src = "worldmap.png"
var mouse_pos_x = [];
var mouse_pos_y = [];
var thickness = 0;
var arraycount = 0;
var mouse_down = false;
var mouse_skip = [];
function update() {}
document.body.onmousedown = function () {
mouse_down = true;
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
arraycount += 1;
}
document.body.onmouseup = function () {
if (mouse_down) {
mouse_down = false;
mouse_skip.push(arraycount);
}
}
document.body.onmousemove = function () {
if (mouse_down) {
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
context.drawImage(img, 0, 0);
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
context.lineWidth = 2.5;
context.strokeStyle = "#00FF00";
context.moveTo(mouse_pos_x[arraycount - 1], mouse_pos_y[arraycount - 1]);
context.lineTo(mouse_pos_x[arraycount], mouse_pos_y[arraycount]);
context.stroke();
arraycount += 1;
var imgdata = context.getImageData(0, 0, a_canvas.width, a_canvas.height);
var l = imgdata.data.length / 4;
for (var i = 0; i < l; i++) {
var r = imgdata.data[i * 4 + 0];
var g = imgdata.data[i * 4 + 1];
var b = imgdata.data[i * 4 + 2];
if (g < 255) {
imgdata.data[i * 4 + 3] = 0;
}
}
context2.putImageData(imgdata, 0, 0);
}
}
setInterval(update, 10);
Now when we remove the draw_image() the green color becomes yellow on the other canvas. But with the draw_image() nothing gets drawn on the second canvas.
What's going wrong? Or do you have a way to do this with other Javascript or not in javascript at all?
Any help would be appreciated!
Luud Janssen & Friends
You can do this with a slightly different approach:
Set the hidden image as CSS background
Draw the cover image on top using context
Change composite mode to destination-out
Anything now drawn will erase instead of draw revealing the (CSS set) image behind
Live demo
The key code (see demo linked above for details):
function start() {
/// draw top image - background image is already set with CSS
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
/// KEY: this will earse where next drawing is drawn
ctx.globalCompositeOperation = 'destination-out';
canvas.onmousedown = handleMouseDown;
canvas.onmousemove = handleMouseMove;
window.onmouseup = handleMouseUp;
}
Then it's just a matter of tracking the mouse position and draw any shape to erase that area, for example a circle:
function erase(x, y) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, pi2);
ctx.fill();
}
Random images for illustrative purposes

Categories

Resources