waterfall: HTML Canvas Waterfall and Spectrum Plot - javascript

I was trying to Build a spectrum and waterfall Plot HTML canvas. After a long research and googling i found this SOURCE CODE
Now i am trying to learn how this code works. i added 3 points on the spectrum using drawPoint2 function.
Can some one please Guild me. thank you.
"use strict";
var colors = [
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(252, 182, 3, 0.31)",
"rgba(3, 103, 252, 0.31)",
"rgba(219, 3, 252, 0.31)",
"rgba(252, 3, 49, 0.31)",
"rgba(221, 48, 232, 0.31)",
];
var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");
Spectrum.prototype.countDecimals = function (value) {
if (Math.floor(value) !== value)
return value.toString().split(".")[1].length || 0;
return 0;
};
Spectrum.prototype.map = function (x, in_min, in_max, out_min, out_max) {
return Math.round(
((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
);
};
Spectrum.prototype.updateSpectrumRatio = function () {
this.spectrumHeight = Math.round(
(this.canvas.height * this.spectrumPercent) / 100.0
);
// create a slop
this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight);
for (var i = 0; i < this.colormap.length; i++) {
var c = this.colormap[this.colormap.length - 1 - i];
this.gradient.addColorStop(
i / this.colormap.length,
"rgba(220,220,220,0.2)"
); //hardcode the patch above Xaxis
}
};
Spectrum.prototype.resize = function () {
var width = this.canvas.clientWidth;
var height = this.canvas.clientHeight;
if (this.canvas.width != width || this.canvas.height != height) {
this.canvas.width = width;
this.canvas.height = height;
this.updateSpectrumRatio();
for (var z = 0; z < this.tags.length; z++) {
this.tags[z].StayX = this.map(
this.tags[z].xval,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray,
0 + crop,
width
); ///////
}
}
if (this.axes.width != width || this.axes.height != this.spectrumHeight) {
this.axes.width = width;
this.axes.height = this.spectrumHeight;
this.updateAxes();
}
};
Spectrum.prototype.updateAxes = function () {
var width = this.ctx_axes.canvas.width + 100; //width of x axis izz
var height = this.ctx_axes.canvas.height;
this.maxScaleX = this.centerHz + this.spanHz / 2;
this.minScaleX = this.centerHz - this.spanHz / 2;
// Clear and fill with black
this.ctx_axes.fillStyle = "black";
this.ctx_axes.fillRect(0, 0, width, height);
// Draw axes
this.ctx_axes.font = "12px Arial";
this.ctx_axes.fillStyle = "white";
this.ctx_axes.textBaseline = "middle";
this.ctx_axes.textAlign = "center"; //change izz
var step = 10; //steps for y-axis
for (var i = this.max_db - 10; i >= this.min_db + 10; i -= step) {
var y = height - this.squeeze(i, 0, height);
this.ctx_axes.fillText(i, 20, y); // height - y
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(22, y); //y axis stroked set izz
this.ctx_axes.lineTo(width, y);
this.ctx_axes.strokeStyle = "rgba(255, 255, 255, 0.15)"; //changed strokes izz
this.ctx_axes.stroke();
}
this.ctx_axes.textBaseline = "bottom";
// change X-axis
var x_axisSteps = 10;
for (var i = 0; i < x_axisSteps; i++) {
var x = Math.round((width - crop) / x_axisSteps - 1) * i + crop;
if (this.spanHz > 0) {
var adjust = 0;
if (i == 0) {
this.ctx_axes.textAlign = "left";
adjust = 3;
} else if (i == 10) {
this.ctx_axes.textAlign = "right";
adjust = -3;
} else {
this.ctx_axes.textAlign = "center";
}
//Graph points in whole points
var freq = this.centerHz + (this.spanHz / 10) * (i - 5);
if (freq > 1e9) {
freq = freq / 1e9;
if (this.countDecimals(freq) > 4) {
freq = freq.toFixed(0);
}
freq = freq + " GHz";
} else if (freq > 1e6) {
freq = freq / 1e6;
if (this.countDecimals(freq) > 4) {
freq = freq.toFixed(0);
}
freq = freq + " MHz"; //this function executed
} else {
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(0);
}
freq = freq + " MHz";
}
this.ctx_axes.fillText(freq, x - 130, height); // x axia height change izz plus values placment
}
//console.log("ctx_axes : ", this.ctx_axes);
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(x, 0);
this.ctx_axes.lineTo(x, height);
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.2)"; //straight gridline on x axis izza
this.ctx_axes.stroke();
}
// const input = prompt("What's your name?");
};
Spectrum.prototype.toggleColor = function () {
this.colorindex++;
if (this.colorindex >= colormaps.length) this.colorindex = 0;
this.colormap = colormaps[this.colorindex];
this.updateSpectrumRatio();
};
Spectrum.prototype.setCenterHz = function (hz) {
this.centerHz = hz;
if (this.center != 0 && this.center != this.centerHz) {
this.centerHz = this.center;
}
this.updateAxes();
};
Spectrum.prototype.setSpanHz = function (hz) {
this.orignalSpanHz = hz;
this.spanHz = hz;
if (this.zoom != 0 && this.spanHz != this.zoom) {
this.spanHz = this.zoom;
}
this.updateAxes();
};
Spectrum.prototype.squeeze = function (value, out_min, out_max) {
if (value <= this.min_db) {
return out_min;
} else if (value >= this.max_db) {
return out_max;
} else {
return Math.round(
((value - this.min_db) / (this.max_db - this.min_db)) * out_max
);
}
};
Spectrum.prototype.squeeze2 = function (value, out_min, out_max) {
if (value <= 30000000) {
return out_min;
} else if (value >= 300000000) {
return out_max;
} else {
return Math.round(
((value - 30000000) / (300000000 - 30000000)) * out_max
);
}
};
Spectrum.prototype.drawRectSpectrogram = function (y, h) {
this.ctx.beginPath();
this.ctx.fillStyle = colors[this.rectColor]; //"rgba(60, 229, 42, 0.31)"; //rect color
this.ctx.strokeStyle = "green";
this.ctx.rect(this.clickRectX, y, this.clickRectWidth, h);
this.ctx.stroke();
this.ctx.fill();
this.ctx.closePath();
};
Spectrum.prototype.threshold = function (y, width, color) {
this.ctx.beginPath();
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 2; //threshold line width
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(width + crop, y);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.lineWidth = 1;
};
function drawPoint2(can, x, y, label) {
can.beginPath();
can.arc(x, y, 5, 0, 2 * Math.PI);
can.fillStyle = "red";
can.fill();
can.fillText(label, x + 10, y);
can.stroke(); // it creates X axies and
}
Spectrum.prototype.drawSpectrum = function (bins) {
//get canvas height and width to draw spectrum ayaz
var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height;
//console.log("spectrum width and height : "+width+" "+ height);
// width of Green Color ayaz
this.ctx.lineWidth = 1; //Amplitude green line width .
//All of the points are 119 nut few points are 118 and few 120 ayaz
if (!this.binsAverage || this.binsAverage.length != bins.length) {
//console.log('this.binsAverage ', this.binsAverage );
this.binsAverage = bins;
} else {
for (var i = 0; i < bins.length; i++) {
this.binsAverage[i] +=
(1 - this.averaging) * (bins[i] - this.binsAverage[i]);
}
}
bins = this.binsAverage;
//Do not draw anything if spectrum is not visible
if (this.ctx_axes.canvas.height < 1) return;
//////////////////////////////////////////////////////////////Creation of X and Y axis completed
// Copy axes from offscreen canvas
this.ctx.drawImage(this.ctx_axes.canvas, -1, -1); // create Spectrums X and Y axis
// // Scale for FFT
this.ctx.save(); // saves sppectrum previous stage
//////////////////////////////////////////////////////////////Creation of X and Y axis completed
this.ctx.scale(width / this.wf_size, 1); //green line visiblity izza
var peakY = this.spectrumHeight;
// Draw FFT bins
this.ctx.beginPath();
this.offset = Math.round(
this.map(crop, 0, this.ctx.canvas.width + 6000, 0, this.wf_size)
); // green line axis set izza
//console.log('this.offset : ', this.offset);
console.log(
"X and Y are : ",
-1 + this.offset + " " + this.spectrumHeight + 0
);
this.ctx.moveTo(-1 + this.offset, this.spectrumHeight + 0); //change for waterfall izza
// above line is the address of left bottom corner of spectrum
// console.log(
// "clkkkkkkkkkkkkkkk : ",
// this.clickRectX+
// crop+
// width+
// this.minScaleX+
// this.maxScaleX
// );
var rangeMin = this.map(
this.clickRectX,
crop,
width,
this.minScaleX,
this.maxScaleX
);
var rangeMax = this.map(
this.clickRectWidth + this.clickRectX,
crop,
width,
this.minScaleX,
this.maxScaleX
);
if (rangeMin > rangeMax) {
var temp;
temp = rangeMax;
rangeMax = rangeMin;
rangeMin = temp;
}
var max = 100000000;
this.scaleY = this.map(this.threshY, this.min_db, this.max_db, height / 2, 0); // 0, height/2 // height of threshold izza
this.detectedFrequency.length = 0;
// console.log('bins are ', bins);
//console.log('new array ');
for (var i = 0; i < bins.length; i++) {
// if ( parseFloat( bins[i]) > -100 ) {
// console.log("bins : ", bins[i]);
// const input = prompt("What's your name?");
// alert(`Your name is ${input}`);
// }
var y =
this.spectrumHeight - 1 - this.squeeze(bins[i], 0, this.spectrumHeight);
peakY = this.squeeze(bins[i], 0, this.spectrumHeight);
if (y > this.spectrumHeight - 1) {
y = this.spectrumHeight - 1;
console.log("Y has chnaged : ", y);
}
if (y < 0) {
y = 0;
console.log("y=0");
}
if (i == 0) {
this.ctx.lineTo(-1 + this.offset, y);//responsible for height of green line
}
// green lines are created here ayaz
// create green lines
// important
//drawPoint2(this.ctx ,i + this.offset, y);
this.ctx.lineTo(i + this.offset, y); // that what point we start drawing green horizental green line
// console.log(
// "Starting Line 1 i " +
// i +
// " X : " +
// parseInt(i + this.offset) +
// " y : " +
// y
// );
if (i == bins.length - 1) {
//drawPoint2(this.ctx ,i + this.offset, y);
this.ctx.lineTo(this.wf_size + 1 + this.offset, y);
// console.log(
// "Second 2 i == bins.length - 1 i " +
// i +
// " Drawingg -1 + this.offset : " +
// parseInt(i + this.offset) +
// " y : " +
// y
// );
}
for (var z = 0; z < this.tags.length; z++) {
if (
i + this.cutfromArray == this.tags[z].xval &&
this.check_click == true
) {
this.tags[z].tagY = y;
this.tags[z].yval = Math.round(bins[i]);
this.tags[z].displayX = Math.round(
this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
);
}
}
if (y < max) {
max = y;
}
let newVal = Math.round(
this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
);
if (this.check_bar) {
if (newVal < rangeMax && newVal > rangeMin) {
if (y < this.scaleY) {
var obj = new Object();
obj.x = newVal;
obj.y = Math.round(bins[i]);
obj.count = 1;
var check = true;
for (var j = 0; j < this.threshRange.length; j++) {
if (
this.threshRange[j].x == obj.x &&
this.threshRange[j].y == obj.y
) {
this.threshRange[j].count++;
check = false;
}
}
if (check) {
let tableRows = document
.getElementById("thresh-table-body")
.getElementsByTagName("tr").length;
if (tableRows < 100) {
this.threshRange.push(obj);
// filling table
let tbody = document.getElementById("thresh-table-body");
let tr = document.createElement("tr");
let td1 = document.createElement("td");
let td2 = document.createElement("td");
let td3 = document.createElement("td");
td1.innerHTML = obj.x; //+" Hz"
td2.innerHTML = obj.y;
td3.innerHTML = obj.count;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tbody.appendChild(tr);
}
} else {
// update table count
for (let c = 0; c < this.threshRange.length; c++) {
let tableRows =
document.getElementById("thresh-table-body").rows[c].cells;
if (
tableRows[0].innerHTML == obj.x &&
tableRows[1].innerHTML == obj.y
) {
let countValue = Number(tableRows[2].innerHTML);
countValue++;
tableRows[2].innerHTML = countValue;
}
}
}
}
}
} else {
if (y < this.scaleY) {
var obj = new Object();
obj.x = newVal;
obj.y = Math.round(bins[i]);
obj.count = 1;
var check = true;
for (var j = 0; j < this.threshRange.length; j++) {
if (
this.threshRange[j].x == obj.x &&
this.threshRange[j].y == obj.y
) {
this.threshRange[j].count++;
check = false;
}
}
}
}
}
// this.ctx.beginPath();
// this.ctx.arc(100, 75, 20, 0, 1 * Math.PI);
// this.ctx.stroke();
//this.ctx.strokeRect(1800,10, 40, 190);
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === obj) {
return true;
}
}
return false;
}
this.ctx.fillStyle = "rgba(0, 0, 0, 0.11)";
// drawPoint2(this.ctx ,
// this.wf_size + 1 + this.offset,
// this.spectrumHeight + 1
// );
this.ctx.lineTo(this.wf_size + 1 + this.offset, this.spectrumHeight + 1);
// console.log(
// "thired 3 this.wf_size + 1 + this.offset : " +
// parseInt(this.wf_size + 1 + this.offset) +
// " this.spectrumHeight + 1 : " +
// parseInt(this.spectrumHeight + 1)
// );
// drawPoint2(this.ctx ,
// this.wf_size + this.offset,
// this.spectrumHeight
// );
this.ctx.lineTo(this.wf_size + this.offset, this.spectrumHeight );
// console.log(
// "Forth this.wf_size + this.offset : " +
// parseInt(this.wf_size + this.offset) +
// " y : " +
// y
// );
if (y < 230 && y > 245) {
console.log("foundddddddddddddd");
}
this.ctx.closePath();
this.ctx.restore();
this.ctx.strokeStyle = "#259a00"; //color of spectrum green
this.ctx.stroke(); // it creates X axies and
/////////////////////////////////////////////////////////////////////////green ended
if (this.spectrumColorCheck) {
this.ctx.fillStyle = this.gradient; //chnage color of under line chart
} else {
this.ctx.fillStyle = "rgba(0, 0, 0, 0.0)";
}
this.ctx.fill();
if (this.check_bar) {
this.drawRectSpectrogram(0, height);
}
var colorTh = "#cc8315"; //By uncomment Change the threshold line color change
this.threshold(this.scaleY, width, colorTh); // yellow light
if (this.check_click == true) {
for (let c = 0; c < this.tags.length; c++) {
this.drawTag(
this.tags[c].StayX,
this.tags[c].tagY,
this.tags[c].displayX,
this.tags[c].yval
);
}
if (this.removeTagCheck == true) {
closeEnough = 30;
for (var z = 0; z < this.tags.length; z++) {
if (this.checkCloseEnough(this.StayX, this.tags[z].StayX + 15)) {
this.tags.splice(z, 1);
z--;
}
}
}
}
closeEnough = 5;
// span hz commented
if (this.updateValueCheck) {
if (this.countDecimals(this.threshY) > 2) {
this.threshY = this.threshY.toFixed(2);
}
this.updateValueCheck = false;
}
var arrt = [ -100000000 , 40000000,35000000];
this.spectrumWidth = Math.round(
(this.canvas.width * this.spectrumPercent) / 100.0
);
for (var k = 0; k < 4; k++) {
var alpha =
this.spectrumHeight - 620 - this.squeeze2(arrt[k], 0, this.spectrumWidth);
drawPoint2(this.ctx, alpha + 600, 150, "A");
}
// draw separate lines
// for (var k=0;k< 4; k++){
// drawPoint2(this.ctx, "200000000", "-80");
// drawPoint2(this.ctx,"100000000", "-80");
// drawPoint2(this.ctx,"35000000", "-80");
// }
//console.log('this.ctx : ',this.ctx);
};
Spectrum.prototype.addData = function (data, bmp) {
if (!this.paused) {
if (data.length > 32768) {
data = this.sequenceResize(data, 32767);
}
this.orignalArrayLength = data.length;
if (this.orignalSpanHz > this.spanHz) {
data = data.slice(
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray
);
}
if (data.length != this.wf_size) {
this.wf_size = data.length;
if (data.length > 32767) {
this.ctx_wf.canvas.width = 32767;
} else {
this.ctx_wf.canvas.width = data.length;
}
this.ctx_wf.fillStyle = "black";
this.ctx_wf.fillRect(0, 0, this.wf.width, 0); //strokes of waterfall
for (var z = 0; z < this.tags.length; z++) {
this.tags[z].StayX = this.map(
this.tags[z].xval,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray,
crop,
this.ctx.canvas.width
);
}
}
this.drawSpectrum(data);
// this.addWaterfallRowBmp(bmp);
// this.addWaterfallRow(data);
this.resize();
}
};
function Spectrum(id, options) {
// Handle options
this.centerHz = options && options.centerHz ? options.centerHz : 0;
this.spanHz = options && options.spanHz ? options.spanHz : 0;
this.wf_size = options && options.wf_size ? options.wf_size : 0;
this.wf_rows = options && options.wf_rows ? options.wf_rows : 50;
this.spectrumPercent =
options && options.spectrumPercent ? options.spectrumPercent : 25;
this.spectrumPercentStep =
options && options.spectrumPercentStep ? options.spectrumPercentStep : 5;
this.averaging = options && options.averaging ? options.averaging : 0.5;
this.rectColor = options && options.rectColor ? options.rectColor : 0;
// Setup state
this.paused = false;
this.fullscreen = true;
this.min_db = -140;
this.max_db = -20;
this.spectrumHeight = 0;
// Colors
this.colorindex = 2;
this.colormap = colormaps[0];
// Create main canvas and adjust dimensions to match actual
this.canvas = document.getElementById("spectrumSM");
canvas.height = this.canvas.clientHeight;
this.canvas.width = this.canvas.clientWidth;
this.ctx = this.canvas.getContext("2d");
this.checkclick = false;
this.clickRectWidth = 1000;
this.dragL = true;
this.dragR = true;
// this.ctx.globalAlpha = 0.1;
this.drag = true;
this.click_x = 0;
this.click_y = 0;
this.check_click = true;
this.threshY = -70;
this.StayX = -10;
this.scaleY = 0;
//for change waterfall design
this.threshCheck = true;
this.removeTagCheck = true;
this.spectrumColorCheck = true;
this.check_bar = true;
this.updateValueCheck = true;
this.tags = [];
this.addTagsCheck = true;
this.maxScaleX = 0;
this.minScaleX = 0;
this.orignalArrayLength = 0;
this.zoom = this.spanHz;
this.center = this.centerHz;
this.threshRange = [];
this.detectedFrequency = [];
this.offset = 10;
this.arraySizeto = 0;
this.orignalSpanHz = 0;
this.cutfromArray = 0;
this.ctx.fillStyle = "black";
this.ctx.fillRect(10, 10, this.canvas.width, this.canvas.height);
// Create offscreen canvas for axes
this.axes = document.createElement("canvas");
this.axes.id = "myCheck";
this.axes.height = 1; // Updated later
this.axes.width = this.canvas.width;
this.ctx_axes = this.axes.getContext("2d");
function myFunction() {
this.style.fontSize = "40px";
}
// Create offscreen canvas for waterfall
this.wf = document.createElement("canvas");
this.wf.height = this.wf_rows;
this.wf.width = this.wf_size;
this.ctx_wf = this.wf.getContext("2d");
// Trigger first render
this.updateSpectrumRatio();
this.resize();
}
Here showing value of spectrum at specific point via click and zoom in/ zoom out functionality.
var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");
Spectrum.prototype.mousedown = function (evt) {
this.checkclick = true;
if (this.checkCloseEnough(this.click_x-3, this.clickRectX)) {
this.dragR = false;
this.dragL = true;
} else if (
this.checkCloseEnough(this.click_x-3, this.clickRectX + this.clickRectWidth)
) {
this.dragR = true;
this.dragL = false;
} else if (this.checkCloseEnough(this.click_y-3, this.scaleY)) {
this.drag = true;
}
};
Spectrum.prototype.mouseup = function (evt) {
this.checkclick = false;
this.dragL = false;
this.dragR = false;
this.drag = false;
if (evt.button === "right") {
this.tags = [];
}
};
Spectrum.prototype.mousemove = function (evt) {
var rect = this.canvas.getBoundingClientRect();
this.click_x = evt.clientX - rect.left;
this.click_y = evt.clientY - rect.top;
closeEnough = Math.abs(this.clickRectWidth);
if (this.dragL == false && this.dragR == false && this.drag == false) {
if (
this.checkclick == true &&
this.checkCloseEnough(
this.click_x,
this.clickRectX + this.clickRectWidth / 2
)
) {
this.clickRectX = this.click_x - this.clickRectWidth / 2;
}
} else if (this.dragL) {
this.clickRectWidth += this.clickRectX - this.click_x;
this.clickRectX = this.click_x;
} else if (this.dragR) {
this.clickRectWidth = -(this.clickRectX - this.click_x);
} else if (this.drag && this.threshCheck) {
this.updateValueCheck = true;
this.threshY = this.map(
this.click_y,
this.canvas.height / 2,
0,
this.min_db,
this.max_db
); // this.max_db, this.min_db
}
closeEnough = 10;
};
Spectrum.prototype.click = function (evt) {
// change izza
this.check_click = true;
this.StayX = this.click_x;
console.log('tag list : ',this.addTagsCheck);
if (this.addTagsCheck == true && this.StayX > 3) {
var tag = {
StayX: this.click_x,
tagY: 0,
yval: 0,
xval: Math.round(
this.map(
this.click_x,
28,
this.ctx.canvas.width,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray
)
),
displayX: 0,
};
this.tags.push(tag);
}
};
Spectrum.prototype.wheel = function (evt) {
this.zoom = this.spanHz;
var inc;
if (this.arraySizeto == 0) {
inc = Math.round(this.orignalArrayLength * 0.05);
} else {
inc = Math.round(this.arraySizeto * 0.05);
}
let zoomInc = 0;
if (this.orignalSpanHz > this.orignalArrayLength) {
zoomInc = this.orignalSpanHz / this.orignalArrayLength;
} else {
zoomInc = this.orignalArrayLength / this.orignalSpanHz;
}
if (evt.deltaY > 0) {
if (
this.orignalSpanHz - (zoomInc * this.cutfromArray - inc * 2) <
this.orignalSpanHz &&
this.cutfromArray - inc >= 0
) {
this.cutfromArray = this.cutfromArray - inc;
this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
} else if (
this.orignalSpanHz + zoomInc * this.cutfromArray * 2 >=
this.orignalSpanHz &&
this.cutfromArray - inc <= 0
) {
this.zoom = this.orignalSpanHz;
this.cutfromArray = 0;
}
} else if (evt.deltaY < 0) {
if (
this.orignalSpanHz - (zoomInc * this.cutfromArray + inc * 2) > inc &&
this.orignalArrayLength - this.cutfromArray * 2 - inc * 2 >
this.ctx.canvas.width
) {
this.cutfromArray = this.cutfromArray + inc;
this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
}
}
this.arraySizeto = this.orignalArrayLength - this.cutfromArray * 2;
this.maxScaleX = this.centerHz + this.zoom / 20;
this.minScaleX = this.centerHz - this.zoom / 20;
};
Spectrum.prototype.undoTag = function () {
this.tags.pop();
};
Spectrum.prototype.checkCloseEnough = function (p1, p2) {
return Math.abs(p1 - p2) < closeEnough;
};
Spectrum.prototype.drawTag = function (locx, locy, xval, yval) {
this.ctx.beginPath();
this.ctx.strokeStyle = "#cc8315";
let freq = xval;
if (freq > 1e9) {
freq = freq / 1e9;
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
freq = freq + " GHz";
} else if (freq > 1e6) {
freq = freq / 1e6;
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
freq = freq + " MHz";
} else {
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
}
var text = " ( " + freq + ", " + yval + ")";
var padding = 5;
var fontSize = 20;
var xPos = locx - padding;
var width = this.ctx.measureText(text).width + padding * 2;
var height = fontSize * 1.286;
var yPos = locy - height / 1.5;
this.ctx.lineWidth = 2;
this.ctx.fillStyle = "rgba(204, 131, 21, 0.8)";
// draw the rect
this.ctx.fillRect(xPos, yPos, width, height);
this.ctx.fillStyle = "white";
this.ctx.font = "bold 10pt droid_serif";
this.ctx.fillText(text, locx, locy);
this.ctx.fillStyle = "white";
this.ctx.beginPath();
this.ctx.arc(locx, locy, 2, 0, Math.PI * 2, true);
this.ctx.fill();
this.ctx.closePath();
};

The spectrum project you reference expects to receive regular updates of an array of data passed into drawSpectrum. Each time it renders that data as a new spectrum in drawFFT, with the data scaled by the numbers set in setRange. Each time it receives data, it also creates a new row of pixels for the waterfall in the rowToImageData function, again scaled by the numbers set in setRange. The previously created rows of pixels are shifted down by one row in addWaterfallRow.
I've made a fiddle that shows how data is handled by the Spectrum object: https://jsfiddle.net/40qun892/
If you run the fiddle it shows two examples, one with three points and another with 100 points of randomly generated data. This line shows how an x-axis is added to the graph:
const spectrumB = new Spectrum("spectrumCanvasB", {spanHz: 5000, centerHz: 2500});
The x-axis is only shown when spanHz is defined. It is generated by drawing 11 labels, equally distributed across the canvas. With the center label based on centerHz, and the remaining labels calculated based on spanHz.
As you can see from the spectrums generated by the fiddle, the x-axis labels are not connected to the data, they are just equally distributed across the canvas.
The graph behind the data is created by applying a scale to the graph so that using the array index will result in data stretched across the graph.
this.ctx.scale(width / <number of data points>, 1);
for (var i = 0; i < <number of data points>.length; i++) {
// lines removed
this.ctx.lineTo(i, y);
// lines removed
}
As you can see from the examples in the fiddle, this doesn't look very nice when there's only three datapoints, because the white lines are stretched in the x-direction but not the y-direction.
Spectrum doesn't care about x-coordinates. It just stretches whatever it is given to fit the canvas. If you give it a spanHz property, it will distribute some labels across the canvas too, but it does not associate them with the data.
The scaling seems to be slightly wrong in Spectrum (which is only noticeable if very few datapoints are used). If I make this change, the points are correctly stretched:
this.ctx.scale(width / (this.wf_size - 1), 1);
Then this change would set the x-axis labels to 100Mhz - 300Mhz:
const spectrumA = new Spectrum("spectrumCanvasA", {spanHz: 200000000, centerHz: 200000000});
Edit: (The relationship between frequency and data)
The only thing the code knows about the frequency is based on the spanHz anad centerHz.
The frequency at an array index is
(<array index> / <number of points on X axis> * <spanHz>) + (<centerHz> / 2)
The frequency at a pixel is
(<x coordinate> / <width of canvas> * <spanHz>) + (<centerHz> / 2)
If you want to convert a frequency to an array index (or a pixel) it would be slightly more complicated, because you would need to find the closest element to that frequency. For example, the pixel at a frequency is:
round((<frequency> - (<centerHz> / 2)) / <spanHz> * <width of canvas>)

Related

want to style the all dots of pre built html canvas background

The code is different, I apologize may I didn't ask the question the right way, you can understand well in principle, When I'm hovering the dots get colours and connect to each other, other dots are invisible, I want that all dots should be visible all time. live example available on codepen, https://codepen.io/tati420/pen/RwBamQo?editors=1111
(function () {
var width,
height,
largeHeader,
canvas,
ctx,
points,
target,
animateHeader = true;
// Main
initHeader();
initAnimation();
addListeners();
function initHeader() {
width = window.innerWidth;
height = window.innerHeight;
target = { x: width / 2, y: height / 2 };
largeHeader = document.getElementById("large-header");
largeHeader.style.height = height + "px";
canvas = document.getElementById("demo-canvas");
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext("2d");
// create points
points = [];
for (var x = 0; x < width; x = x + width / 20) {
for (var y = 0; y < height; y = y + height / 20) {
var px = x + (Math.random() * width) / 20;
var py = y + (Math.random() * height) / 20;
var p = { x: px, originX: px, y: py, originY: py };
points.push(p);
}
}
// for each point find the 5 closest points
for (var i = 0; i < points.length; i++) {
var closest = [];
var p1 = points[i];
for (var j = 0; j < points.length; j++) {
var p2 = points[j];
if (!(p1 == p2)) {
var placed = false;
for (var k = 0; k < 5; k++) {
if (!placed) {
if (closest[k] == undefined) {
closest[k] = p2;
placed = true;
}
}
}
for (var k = 0; k < 5; k++) {
if (!placed) {
if (getDistance(p1, p2) < getDistance(p1, closest[k])) {
closest[k] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for (var i in points) {
var c = new Circle(
points[i],
2 + Math.random() * 2,
"rgba(0,255,255,0.3)"
);
points[i].circle = c;
}
}
// Event handling
function addListeners() {
if (!("ontouchstart" in window)) {
window.addEventListener("mousemove", mouseMove);
}
window.addEventListener("scroll", scrollCheck);
window.addEventListener("resize", resize);
}
function mouseMove(e) {
var posx = (posy = 0);
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
posy =
e.clientY +
document.body.scrollTop +
document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if (document.body.scrollTop > height) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
largeHeader.style.height = height + "px";
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for (var i in points) {
shiftPoint(points[i]);
}
}
function animate() {
if (animateHeader) {
ctx.clearRect(0, 0, width, height);
for (var i in points) {
// detect points in range
if (Math.abs(getDistance(target, points[i])) < 4000) {
points[i].active = 0.3;
points[i].circle.active = 0.6;
} else if (Math.abs(getDistance(target, points[i])) < 20000) {
points[i].active = 0.1;
points[i].circle.active = 0.3;
} else if (Math.abs(getDistance(target, points[i])) < 40000) {
points[i].active = 0.02;
points[i].circle.active = 0.1;
} else {
points[i].active = 0;
points[i].circle.active = 0;
}
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
}
function shiftPoint(p) {
TweenLite.to(p, 1 + 1 * Math.random(), {
x: p.originX - 50 + Math.random() * 100,
y: p.originY - 50 + Math.random() * 100,
ease: Circ.easeInOut,
onComplete: function () {
shiftPoint(p);
},
});
}
ctx.strokeStyle = "rgba(255,0,0," + p.active + ")";
// Canvas manipulation
function drawLines(p) {
if (!p.active) return;
for (var i in p.closest) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.closest[i].x, p.closest[i].y);
ctx.strokeStyle = "rgba(0,255,255," + p.active + ")";
ctx.stroke();
}
}
function Circle(pos, rad, color) {
var _this = this;
// constructor
(function () {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
})();
this.draw = function () {
if (!_this.active) return;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = "rgba(0,255,0," + _this.active + ")";
ctx.fill();
};
}
// Util
function getDistance(p1, p2) {
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
}
})();
If I understand your question correctly, you just want to make sure that every point.active = 1 and point.circle.active = 1:
function animate() {
if (animateHeader) {
ctx.clearRect(0, 0, width, height);
for (var i in points) {
points[i].active = 1;
points[i].circle.active = 1;
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
}

Trim white space around canvas on signature pad downloaded image

I have implemented signature pad.js to my website. I want to trim white spaces around drew signature. I'm now trimming transparent unused area surround my canvas. I have tried making canvas background white. Then this code isn't detecting the drew points boundaries.
But, I need to make the background white
Here's what I'm getting now :-
(signature)
====>
(after trimmed)
I'm using below code to trim transparent background surrounded the signature:
function trimCanvas(c) {
var ctx = c.getContext('2d'),
copy = document.createElement('canvas').getContext('2d'),
pixels = ctx.getImageData(0, 0, c.width, c.height),
l = pixels.data.length,
i,
bound = {
top: null,
left: null,
right: null,
bottom: null
},
x, y;
// Iterate over every pixel to find the highest
// and where it ends on every axis ()
for (i = 0; i < l; i += 4) {
if (pixels.data[i + 3] !== 0) {
x = (i / 4) % c.width;
y = ~~((i / 4) / c.width);
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
// Calculate the height and width of the content
var trimHeight = bound.bottom - bound.top,
trimWidth = bound.right - bound.left,
trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
copy.canvas.width = trimWidth;
copy.canvas.height = trimHeight;
copy.putImageData(trimmed, 0, 0);
// Return trimmed canvas
return copy.canvas;
}
I got it solved by implementing below function.
SignaturePad.prototype.removeBlanks = function () {
var imgWidth = this._ctx.canvas.width;
var imgHeight = this._ctx.canvas.height;
var imageData = this._ctx.getImageData(0, 0, imgWidth, imgHeight),
data = imageData.data,
getAlpha = function(x, y) {
return {
red: data[(imgWidth*y + x) * 4],
green: data[(imgWidth*y + x) * 4 + 1],
blue: data[(imgWidth*y + x) * 4 + 2]
};
},
isWhite = function (rgb) {
return rgb.red == 255 && rgb.green == 255 && rgb.blue == 255;
},
scanY = function (fromTop) {
var offset = fromTop ? 1 : -1;
// loop through each row
for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {
// loop through each column
for(var x = 0; x < imgWidth; x++) {
if (!isWhite(getAlpha(x, y))) {
return y;
}
}
}
return null; // all image is white
},
scanX = function (fromLeft) {
var offset = fromLeft? 1 : -1;
// loop through each column
for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {
// loop through each row
for(var y = 0; y < imgHeight; y++) {
if (!isWhite(getAlpha(x, y))) {
return x;
}
}
}
return null; // all image is white
};
var cropTop = scanY(false),
cropBottom = scanY(true),
cropLeft = scanX(true),
cropRight = scanX(false);
cropTop+=30;
cropRight+=20;
var relevantData = this._ctx.getImageData(cropLeft-10, cropTop-20, cropRight-cropLeft, cropBottom-cropTop);
this._ctx.canvas.width = cropRight-cropLeft;
this._ctx.canvas.height = cropBottom-cropTop;
this._ctx.clearRect(0, 0, cropRight-cropLeft, cropBottom-cropTop);
this._ctx.putImageData(relevantData, 0, 0);
};
And I use blow code to call to the function
signaturePad.removeBlanks();
var dataURL = signaturePad.toDataURL('image/png');

get boundaries of complex object in image with white background (js)

What is a good way of getting the boundaries for an image (not the image itself, but rather the non-white pixels)? I am using javascript, so try to keep the algos within that realm if you can.
For example, how would I get a list/two lists of all of the x and y points where for the points that exist on the boundary of this (group of) object(s):
Note that the interior part should be included, but a lack of colour that is completely on the inside (like a hole) should be excluded.
Therefore, the result would be two lists that contain the x and y points (for the pixels) that would construct an object similar to this:
Below is how I "achieved" it. While it works for all concave objects, if you try to use it for more complex objects with some convex sides, it inevitably fails.
Success with Concave Object
jsfiddle: https://jsfiddle.net/bhc4qn87/
snippet:
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = _PI / 7;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "8px";
_tmpCan.style.top = "8px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum < 759) {
firstY = y;
}
} else {
if (sum < 759) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
Failure with Complex Object
jsfiddle: https://jsfiddle.net/xdr9bt0w/
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = 0;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0xc36843, opacity: .7 } );
var cube2 = new THREE.Mesh( geometry, material );
cube2.position.x = -.75;
cube2.position.y = .75
_scene.add( cube2 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x43f873, opacity: .7 } );
var cube3 = new THREE.Mesh( geometry, material );
cube3.position.x = -.25;
cube3.position.y = 1.5;
_scene.add( cube3 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x253621, opacity: .7 } );
var cube4 = new THREE.Mesh( geometry, material );
cube4.position.x = 1;
cube4.position.y = .35;
_scene.add( cube4 );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "200px";
_tmpCan.style.top = "0px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum > 3) {
firstY = y;
}
} else {
if (sum > 3) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
cube2.rotation.x -= 0.001;
cube2.rotation.y += 0.001;
cube3.rotation.x += 0.001;
cube3.rotation.y -= 0.001;
cube4.rotation.x -= 0.001;
cube4.rotation.y -= 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
You can see with the above jsfiddle that the inside of this complex, convex image fails on the inside.
Question
Therefore, the question remains: what is a good way of creating a mask, if you will, of the image (disregarding holes) that will cover all of the outside of any complex/convex object where the background is white and the components of the image are anything but white? thanks
Here is a solution that uses a flood-fill algorithm to cover the outer areas with white and the rest with black.
Keep in mind that this is a very naive implementation, there are lots of optimization that can potentially be done (by calculating the bounding rectangle and only filling inside it for example, another one would be to use 32-bit arrays to do the actual pixel assignment while filling).
Another thing to note is that the filling always starts in the upper left corner, if the object is currently covering that pixel it will not work (you can however pick another pixel to start at).
I removed the touch handlers and some other items to keep the example short.
The updateMask-function is where the mask is created.
function createCube(color, x, y){
const geo = new THREE.BoxBufferGeometry( 1, 1, 1 );
const mat = new THREE.MeshPhysicalMaterial( { color: color, opacity: 1 } );
const mesh = new THREE.Mesh(geo, mat);
mesh.position.x = x;
mesh.position.y = y;
return mesh;
}
const c_main = document.getElementById("main");
const c_mask = document.getElementById("mask");
const ctx_mask = c_mask.getContext("2d");
ctx_mask.fillStyle = "#000";
const cw = c_main.width, ch = c_main.height;
const TWO_PI = Math.PI * 2;
const damp = 75, radius = 10, animspeed = 0.001;
const center = new THREE.Vector3(0, 0, 0);
let x1 = 0;
let phi = Math.PI / 2;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, cw / ch, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas: c_main, alpha: true, antialias: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(cw, ch);
const camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
scene.add(new THREE.AmbientLight(0x404040));
scene.add(camLight);
const cubes = [];
cubes.push(createCube(0x2378d3, 0, 0));
cubes.push(createCube(0xc36843, -0.75, 0.75));
cubes.push(createCube(0x43f873, -0.25, 1.5));
cubes.push(createCube(0x253621, 1, 0.35));
scene.add(...cubes);
function initialize() {
c_main.addEventListener("mousedown", mouseDown, false);
updateCamera();
animate();
}
function updateMask(){
//First, fill the canvas with black
ctx_mask.globalCompositeOperation = "source-over";
ctx_mask.fillRect(0,0, cw, ch);
//Then using the composite operation "destination-in" the canvas is made transparent EXCEPT where the new image is drawn.
ctx_mask.globalCompositeOperation = "destination-in";
ctx_mask.drawImage(c_main, 0, 0);
//Now, use a flood fill algorithm of your choice to fill the outer transparent field with white.
const idata = ctx_mask.getImageData(0,0, cw, ch);
const array = idata.data;
floodFill(array, 0, 0, cw, ch);
ctx_mask.putImageData(idata, 0, 0);
//The only transparency left are in the "holes", we make these black by using the composite operation "destination-over" to paint black behind everything.
ctx_mask.globalCompositeOperation = "destination-over";
ctx_mask.fillRect(0,0, cw, ch);
}
function mouseDown(e){
e.preventDefault();
x1 = e.pageX;
const button = e.button;
document.addEventListener("mousemove", mouseMove, false);
document.addEventListener("mouseup", mouseUp, false);
}
function mouseUp(){
document.removeEventListener("mousemove", mouseMove, false);
document.removeEventListener("mouseup", mouseUp, false);
}
function mouseMove(e){
const x2 = e.pageX;
const dx = x2 - x1;
phi += dx/damp;
phi %= TWO_PI;
if( phi < 0 ){
phi += TWO_PI;
}
x1 = x2;
updateCamera();
}
function updateCamera() {
const x = radius * Math.cos(phi);
const y = 0;
const z = radius * Math.sin(phi);
camera.position.set(x, y, z);
camera.lookAt(center);
camLight.position.set(x, y, z);
}
function animate(){
cubes[0].rotation.x += animspeed;
cubes[0].rotation.y += animspeed;
cubes[1].rotation.x -= animspeed;
cubes[1].rotation.y += animspeed;
cubes[2].rotation.x += animspeed;
cubes[2].rotation.y -= animspeed;
cubes[3].rotation.x -= animspeed;
cubes[3].rotation.y -= animspeed;
renderer.render(scene, camera);
updateMask();
requestAnimationFrame(animate);
}
const FILL_THRESHOLD = 254;
//Quickly adapted flood fill from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html
function floodStart(array, x, y, width, height){
const M = width * 4;
while(true){
let ox = x, oy = y;
while(y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD){ y--; }
while(x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD){ x--; }
if(x === ox && y === oy){ break; }
}
floodFill(array, x, y, width, height);
}
function floodFill(array, x, y, width, height){
const M = width * 4;
let lastRowLength = 0;
do{
let rowLength = 0, sx = x;
let idx = y*M + x*4 + 3;
if(lastRowLength !== 0 && array[idx] >= FILL_THRESHOLD){
do{
if(--lastRowLength === 0){ return; }
}
while(array[ y*M + (++x)*4 + 3]);
sx = x;
}
else{
for(; x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD; rowLength++, lastRowLength++){
const idx = y*M + (--x)*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
if( y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD ){
floodStart(array, x, y-1, width, height);
}
}
}
for(; sx < width && array[y*M + sx*4 + 3] < FILL_THRESHOLD; rowLength++, sx++){
const idx = y*M + sx*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
}
if(rowLength < lastRowLength){
for(let end=x+lastRowLength; ++sx < end; ){
if(array[y*M + sx*4 + 3] < FILL_THRESHOLD){
floodFill(array, sx, y, width, height);
}
}
}
else if(rowLength > lastRowLength && y !== 0){
for(let ux=x+lastRowLength; ++ux<sx; ){
if(array[(y-1)*M + ux*4 + 3] < FILL_THRESHOLD){
floodStart(array, ux, y-1, width, height);
}
}
}
lastRowLength = rowLength;
}
while(lastRowLength !== 0 && ++y < height);
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="main" width="300" height="200"></canvas>
<canvas id="mask" width="300" height="200"></canvas>

createPattern on canvas issue

As on the attached fiddle, the background image on the canvas is just coming and getting disappearing
I tried both createPattern and drawImage both are having same issue.
I am not an expert in Canvas. Any help would be appreciated
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
*/
var imageObj = new Image();
imageObj.onload = function() {
var pattern = ctx.createPattern(imageObj, 'repeat');
ctx.rect(0, 0, width, height);
ctx.fillStyle = pattern;
ctx.fill();
ctx.save();
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/wood-pattern.png';
with (ctx) {
fillStyle = '#ccc';
fillRect(0, 0, width, height);
fillStyle = '#999999';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 128;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var a, b, data, cur_pixel, new_pixel, old_data;
var t = oldind; oldind = newind; newind = t;
var i = 0;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind];
data -= data >> 5;
_ripplemap[_newind] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++i;
}
}
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
var timeOut;
var intrvl = setInterval(function() {
clearTimeout(timeOut);
disturb(0, (height/40));
disturb((width/2.67), (height/40));
disturb((width/1.33), (height/40));
disturb(0, (height/2.67));
disturb((width/2.67), (height/2.67));
disturb((width/1.33), (height/2.67));
disturb(0, (height/1.33));
disturb((width/2.67), (height/1.33));
disturb((width/1.33), (height/1.33));
timeOut= setTimeout(function(){
disturb((width/1.8), (height/8));
disturb((width/-1.2), (height/8));
disturb((width/1.14), (height/8));
disturb((width/1.8), (height/2.13));
disturb((width/-1.2), (height/2.13));
disturb((width/1.14), (height/2.13));
disturb((width/1.8), (height/1.03));
disturb((width/-1.2), (height/1.03));
disturb((width/1.14), (height/1.03));;
},300);
}, 700);
setTimeout(function(){
clearInterval(intrvl);
},3000);
})();
this is the link to the fiddle

Delete or hide the image in previous frame - html 5 canvas

I'm trying to edit a pacman implementation (source) using htlm5 and js. The problem is when I make the background transparent, the ghosts and pacman images holding the previews frames. This is an example about my problem.
My js code:
Pacman.FPS = 30;
Pacman.Ghost = function (game, map, colour) {
function getColour() {
if (eatable) {
if (secondsAgo(eatable) > 5) {
return game.getTick() % 20 > 10 ? "rgba(0, 0, 0, 0.1)" : "rgba(0, 0, 0, 0.1)";
} else {
return "rgba(0, 0, 0, 0.1)";
}
} else if(eaten) {
return "#222";
}
return colour;
};
function draw(ctx) {
var s = map.blockSize,
top = (position.y/10) * s,
left = (position.x/10) * s;
if (eatable && secondsAgo(eatable) > 8) {
eatable = null;
}
if (eaten && secondsAgo(eaten) > 3) {
eaten = null;
}
var tl = left + s;
var base = top + s - 3;
var inc = s / 10;
var high = game.getTick() % 10 > 5 ? 3 : -3;
var low = game.getTick() % 10 > 5 ? -3 : 3;
ctx.fillStyle = getColour();
ctx.beginPath();
ctx.moveTo(left, base);
ctx.quadraticCurveTo(left, top, left + (s/2), top);
ctx.quadraticCurveTo(left + s, top, left+s, base);
// Wavy things at the bottom
ctx.quadraticCurveTo(tl-(inc*1), base+high, tl - (inc * 2), base);
ctx.quadraticCurveTo(tl-(inc*3), base+low, tl - (inc * 4), base);
ctx.quadraticCurveTo(tl-(inc*5), base+high, tl - (inc * 6), base);
ctx.quadraticCurveTo(tl-(inc*7), base+low, tl - (inc * 8), base);
ctx.quadraticCurveTo(tl-(inc*9), base+high, tl - (inc * 10), base);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#FFF";
ctx.arc(left + 6,top + 6, s / 6, 0, 300, false);
ctx.arc((left + s) - 6,top + 6, s / 6, 0, 300, false);
ctx.closePath();
ctx.fill();
var f = s / 12;
var off = {};
off[RIGHT] = [f, 0];
off[LEFT] = [-f, 0];
off[UP] = [0, -f];
off[DOWN] = [0, f];
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.arc(left+6+off[direction][0], top+6+off[direction][3],
s / 15, 0, 300, false);
ctx.arc((left+s)-6+off[direction][0], top+6+off[direction][4],
s / 15, 0, 300, false);
ctx.closePath();
ctx.fill();
};
function pane(pos) {
if (pos.y === 100 && pos.x >= 190 && direction === RIGHT) {
return {"y": 100, "x": -10};
}
if (pos.y === 100 && pos.x <= -10 && direction === LEFT) {
return position = {"y": 100, "x": 190};
}
return false;
};
function move(ctx) {
var oldPos = position,
onGrid = onGridSquare(position),
npos = null;
if (due !== direction) {
npos = getNewCoord(due, position);
if (onGrid &&
map.isFloorSpace({
"y":pointToCoord(nextSquare(npos.y, due)),
"x":pointToCoord(nextSquare(npos.x, due))})) {
direction = due;
} else {
npos = null;
}
}
if (npos === null) {
npos = getNewCoord(direction, position);
}
if (onGrid &&
map.isWallSpace({
"y" : pointToCoord(nextSquare(npos.y, direction)),
"x" : pointToCoord(nextSquare(npos.x, direction))
})) {
due = getRandomDirection();
return move(ctx);
}
position = npos;
var tmp = pane(position);
if (tmp) {
position = tmp;
}
due = getRandomDirection();
return {
"new" : position,
"old" : oldPos
};
};
return {
"eat" : eat,
"isVunerable" : isVunerable,
"isDangerous" : isDangerous,
"makeEatable" : makeEatable,
"reset" : reset,
"move" : move,
"draw" : draw
};
};
Pacman.User = function (game, map) {
var position = null,
direction = null,
eaten = null,
due = null,
lives = null,
score = 5,
keyMap = {};
keyMap[KEY.ARROW_LEFT] = LEFT;
keyMap[KEY.ARROW_UP] = UP;
keyMap[KEY.ARROW_RIGHT] = RIGHT;
keyMap[KEY.ARROW_DOWN] = DOWN;
function addScore(nScore) {
score += nScore;
if (score >= 10000 && score - nScore < 10000) {
lives += 1;
}
};
function theScore() {
return score;
};
function loseLife() {
lives -= 1;
};
function getLives() {
return lives;
};
function initUser() {
score = 0;
lives = 3;
newLevel();
}
function newLevel() {
resetPosition();
eaten = 0;
};
function resetPosition() {
position = {"x": 90, "y": 120};
direction = LEFT;
due = LEFT;
};
function reset() {
initUser();
resetPosition();
};
function keyDown(e) {
if (typeof keyMap[e.keyCode] !== "undefined") {
due = keyMap[e.keyCode];
e.preventDefault();
e.stopPropagation();
return false;
}
return true;
};
function getNewCoord(dir, current) {
return {
"x": current.x + (dir === LEFT && -2 || dir === RIGHT && 2 || 0),
"y": current.y + (dir === DOWN && 2 || dir === UP && -2 || 0)
};
};
function onWholeSquare(x) {
return x % 10 === 0;
};
function pointToCoord(x) {
return Math.round(x/10);
};
function nextSquare(x, dir) {
var rem = x % 10;
if (rem === 0) {
return x;
} else if (dir === RIGHT || dir === DOWN) {
return x + (10 - rem);
} else {
return x - rem;
}
};
function next(pos, dir) {
return {
"y" : pointToCoord(nextSquare(pos.y, dir)),
"x" : pointToCoord(nextSquare(pos.x, dir)),
};
};
function onGridSquare(pos) {
return onWholeSquare(pos.y) && onWholeSquare(pos.x);
};
function isOnSamePlane(due, dir) {
return ((due === LEFT || due === RIGHT) &&
(dir === LEFT || dir === RIGHT)) ||
((due === UP || due === DOWN) &&
(dir === UP || dir === DOWN));
};
function move(ctx) {
var npos = null,
nextWhole = null,
oldPosition = position,
block = null;
if (due !== direction) {
npos = getNewCoord(due, position);
if (isOnSamePlane(due, direction) ||
(onGridSquare(position) &&
map.isFloorSpace(next(npos, due)))) {
direction = due;
} else {
npos = null;
}
}
if (npos === null) {
npos = getNewCoord(direction, position);
}
if (onGridSquare(position) && map.isWallSpace(next(npos, direction))) {
direction = NONE;
}
if (direction === NONE) {
return {"new" : position, "old" : position};
}
if (npos.y === 100 && npos.x >= 190 && direction === RIGHT) {
npos = {"y": 100, "x": -10};
}
if (npos.y === 100 && npos.x <= -12 && direction === LEFT) {
npos = {"y": 100, "x": 190};
}
position = npos;
nextWhole = next(position, direction);
block = map.block(nextWhole);
if ((isMidSquare(position.y) || isMidSquare(position.x)) &&
block === Pacman.BISCUIT || block === Pacman.PILL) {
map.setBlock(nextWhole, Pacman.EMPTY);
addScore((block === Pacman.BISCUIT) ? 10 : 50);
eaten += 1;
if (eaten === 182) {
game.completedLevel();
}
if (block === Pacman.PILL) {
game.eatenPill();
}
}
return {
"new" : position,
"old" : oldPosition
};
};
function isMidSquare(x) {
var rem = x % 10;
return rem > 3 || rem < 7;
};
function calcAngle(dir, pos) {
if (dir == RIGHT && (pos.x % 10 < 5)) {
return {"start":0.25, "end":1.75, "direction": false};
} else if (dir === DOWN && (pos.y % 10 < 5)) {
return {"start":0.75, "end":2.25, "direction": false};
} else if (dir === UP && (pos.y % 10 < 5)) {
return {"start":1.25, "end":1.75, "direction": true};
} else if (dir === LEFT && (pos.x % 10 < 5)) {
return {"start":0.75, "end":1.25, "direction": true};
}
return {"start":0, "end":2, "direction": false};
};
function drawDead(ctx, amount) {
var size = map.blockSize,
half = size / 2;
if (amount >= 1) {
return;
}
ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(((position.x/10) * size) + half,
((position.y/10) * size) + half);
ctx.arc(((position.x/10) * size) + half,
((position.y/10) * size) + half,
half, 0, Math.PI * 2 * amount, true);
ctx.fill();
};
function draw(ctx) {
var s = map.blockSize,
angle = calcAngle(direction, position);
ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(((position.x/10) * s) + s / 2,
((position.y/10) * s) + s / 2);
ctx.arc(((position.x/10) * s) + s / 2,
((position.y/10) * s) + s / 2,
s / 2, Math.PI * angle.start,
Math.PI * angle.end, angle.direction);
ctx.fill();
};
initUser();
return {
"draw" : draw,
"drawDead" : drawDead,
"loseLife" : loseLife,
"getLives" : getLives,
"score" : score,
"addScore" : addScore,
"theScore" : theScore,
"keyDown" : keyDown,
"move" : move,
"newLevel" : newLevel,
"reset" : reset,
"resetPosition" : resetPosition
};
};
Pacman.Map = function (size) {
var height = null,
width = null,
blockSize = size,
pillSize = 0,
map = null;
function withinBounds(y, x) {
return y >= 0 && y < height && x >= 0 && x < width;
}
function isWall(pos) {
return withinBounds(pos.y, pos.x) && map[pos.y][pos.x] === Pacman.WALL;
}
function isFloorSpace(pos) {
if (!withinBounds(pos.y, pos.x)) {
return false;
}
var peice = map[pos.y][pos.x];
return peice === Pacman.EMPTY ||
peice === Pacman.BISCUIT ||
peice === Pacman.PILL;
}
function drawWall(ctx) {
var i, j, p, line;
ctx.strokeStyle = "#fFF";
ctx.lineWidth = 5;
ctx.lineCap = "round";
for (i = 0; i < Pacman.WALLS.length; i += 1) {
line = Pacman.WALLS[i];
ctx.beginPath();
for (j = 0; j < line.length; j += 1) {
p = line[j];
if (p.move) {
ctx.moveTo(p.move[0] * blockSize, p.move[1] * blockSize);
} else if (p.line) {
ctx.lineTo(p.line[0] * blockSize, p.line[1] * blockSize);
} else if (p.curve) {
ctx.quadraticCurveTo(p.curve[0] * blockSize,
p.curve[1] * blockSize,
p.curve[2] * blockSize,
p.curve[3] * blockSize);
}
}
ctx.stroke();
}
}
function reset() {
map = Pacman.MAP.clone();
height = map.length;
width = map[0].length;
};
function block(pos) {
return map[pos.y][pos.x];
};
function setBlock(pos, type) {
map[pos.y][pos.x] = type;
};
function drawPills(ctx) {
if (++pillSize > 30) {
pillSize = 0;
}
for (i = 0; i < height; i += 1) {
for (j = 0; j < width; j += 1) {
if (map[i][j] === Pacman.PILL) {
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect((j * blockSize), (i * blockSize),
blockSize, blockSize);
ctx.fillStyle = "#FFF";
ctx.arc((j * blockSize) + blockSize / 2,
(i * blockSize) + blockSize / 2,
Math.abs(5 - (pillSize/3)),
0,
Math.PI * 2, false);
ctx.fill();
ctx.closePath();
}
}
}
};
function draw(ctx) {
var i, j, size = blockSize;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect(0, 0, width * size, height * size);
drawWall(ctx);
for (i = 0; i < height; i += 1) {
for (j = 0; j < width; j += 1) {
drawBlock(i, j, ctx);
}
}
};
function drawBlock(y, x, ctx) {
var layout = map[y][x];
if (layout === Pacman.PILL) {
return;
}
ctx.beginPath();
if (layout === Pacman.EMPTY || layout === Pacman.BLOCK ||
layout === Pacman.BISCUIT) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect((x * blockSize), (y * blockSize),
blockSize, blockSize);
if (layout === Pacman.BISCUIT) {
ctx.fillStyle = "#FFF";
ctx.fillRect((x * blockSize) + (blockSize / 2.5),
(y * blockSize) + (blockSize / 2.5),
blockSize / 6, blockSize / 6);
}
}
ctx.closePath();
};
reset();
return {
"draw" : draw,
"drawBlock" : drawBlock,
"drawPills" : drawPills,
"block" : block,
"setBlock" : setBlock,
"reset" : reset,
"isWallSpace" : isWall,
"isFloorSpace" : isFloorSpace,
"height" : height,
"width" : width,
"blockSize" : blockSize
};
};
Pacman.Audio = function(game) {
var files = [],
endEvents = [],
progressEvents = [],
playing = [];
function load(name, path, cb) {
var f = files[name] = document.createElement("audio");
progressEvents[name] = function(event) { progress(event, name, cb); };
f.addEventListener("canplaythrough", progressEvents[name], true);
f.setAttribute("preload", "true");
f.setAttribute("autobuffer", "true");
f.setAttribute("src", path);
f.pause();
};
function progress(event, name, callback) {
if (event.loaded === event.total && typeof callback === "function") {
callback();
files[name].removeEventListener("canplaythrough",
progressEvents[name], true);
}
};
function disableSound() {
for (var i = 0; i < playing.length; i++) {
files[playing[i]].pause();
files[playing[i]].currentTime = 0;
}
playing = [];
};
function ended(name) {
var i, tmp = [], found = false;
files[name].removeEventListener("ended", endEvents[name], true);
for (i = 0; i < playing.length; i++) {
if (!found && playing[i]) {
found = true;
} else {
tmp.push(playing[i]);
}
}
playing = tmp;
};
return {
"disableSound" : disableSound,
"load" : load,
"play" : play,
"pause" : pause,
"resume" : resume
};
};
var PACMAN = (function () {
var state = WAITING,
audio = null,
ghosts = [],
ghostSpecs = ["#00FFDE", "#FF0000", "#FFB8DE", "#FFB847"],
eatenCount = 0,
level = 0,
tick = 0,
ghostPos, userPos,
stateChanged = true,
timerStart = null,
lastTime = 0,
ctx = null,
timer = null,
map = null,
user = null,
stored = null;
function getTick() {
return tick;
};
function collided(user, ghost) {
return (Math.sqrt(Math.pow(ghost.x - user.x, 2) +
Math.pow(ghost.y - user.y, 2))) < 10;
};
function drawFooter() {
var topLeft = (map.height * map.blockSize),
textBase = topLeft + 17;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000000";
ctx.fillRect(0, topLeft, (map.width * map.blockSize), 30);
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#FFFF00";
for (var i = 0, len = user.getLives(); i < len; i++) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(150 + (25 * i) + map.blockSize / 2,
(topLeft+1) + map.blockSize / 2);
ctx.arc(150 + (25 * i) + map.blockSize / 2,
(topLeft+1) + map.blockSize / 2,
map.blockSize / 2, Math.PI * 0.25, Math.PI * 1.75, false);
ctx.fill();
}
ctx.fillStyle = !soundDisabled() ? "#00FF00" : "#FF0000";
ctx.font = "bold 16px sans-serif";
//ctx.fillText("♪", 10, textBase);
ctx.fillText("s", 10, textBase);
ctx.fillStyle = "#FFF";
ctx.font = "14px BDCartoonShoutRegular";
ctx.fillText("Score: " + user.theScore(), 30, textBase);
ctx.fillText("Level: " + level, 260, textBase);
}
function redrawBlock(pos) {
map.drawBlock(Math.floor(pos.y/10), Math.floor(pos.x/10), ctx);
map.drawBlock(Math.ceil(pos.y/10), Math.ceil(pos.x/10), ctx);
}
function mainDraw() {
var diff, u, i, len, nScore;
ghostPos = [];
for (i = 0, len = ghosts.length; i < len; i += 1) {
ghostPos.push(ghosts[i].move(ctx));
}
u = user.move(ctx);
for (i = 0, len = ghosts.length; i < len; i += 1) {
redrawBlock(ghostPos[i].old);
}
redrawBlock(u.old);
for (i = 0, len = ghosts.length; i < len; i += 1) {
ghosts[i].draw(ctx);
}
user.draw(ctx);
userPos = u["new"];
for (i = 0, len = ghosts.length; i < len; i += 1) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
if (collided(userPos, ghostPos[i]["new"])) {
if (ghosts[i].isVunerable()) {
audio.play("eatghost");
ghosts[i].eat();
eatenCount += 1;
nScore = eatenCount * 50;
drawScore(nScore, ghostPos[i]);
user.addScore(nScore);
setState(EATEN_PAUSE);
timerStart = tick;
} else if (ghosts[i].isDangerous()) {
audio.play("die");
setState(DYING);
timerStart = tick;
}
}
}
};
function mainLoop() {
var diff;
if (state !== PAUSE) {
++tick;
}
map.drawPills(ctx);
if (state === PLAYING) {
mainDraw();
} else if (state === WAITING && stateChanged) {
stateChanged = false;
map.draw(ctx);
dialog("Press N to start a New game");
} else if (state === EATEN_PAUSE &&
(tick - timerStart) > (Pacman.FPS / 3)) {
map.draw(ctx);
setState(PLAYING);
} else if (state === DYING) {
if (tick - timerStart > (Pacman.FPS * 2)) {
loseLife();
} else {
redrawBlock(userPos);
for (i = 0, len = ghosts.length; i < len; i += 1) {
redrawBlock(ghostPos[i].old);
//ctx.fillStyle = "rgba(0, 0, 0, 0)";
ghostPos.push(ghosts[i].draw(ctx));
}
user.drawDead(ctx, (tick - timerStart) / (Pacman.FPS * 2));
}
} else if (state === COUNTDOWN) {
diff = 5 + Math.floor((timerStart - tick) / Pacman.FPS);
if (diff === 0) {
map.draw(ctx);
setState(PLAYING);
} else {
if (diff !== lastTime) {
lastTime = diff;
map.draw(ctx);
dialog("Starting in: " + diff);
}
}
}
drawFooter();
}
}());
For every game you want to make, your gameLoop should contain game logic like this (and in advance: GameAlchemist is right, everything should be redrawn every frame):
Clear the whole canvas
Draw background elements (don't use expensive calls, simply draw an image. If you have a lot of drawn elements, such as shapes, lines, etc., make sure to buffer this first on f.e. another, hidden canvas)
Draw more static (background) elements if you want to, that don't change position (f.e. walls)
Draw dynamic elements (your Hero, enemies, bullets, etc.)
Think of all these steps as layers (as in f.e. Photoshop) and make sure to do it in the right order.
This cheat sheet is very helpful.
Also, instead of setInterval, start using requestAnimationFrame. See f.e. this link.
PS Please don't beg for code, in the way you do. Experiment, try, fail, and try again. Then you will learn. Not by asking for gift wrapped pieces of code.

Categories

Resources