I wrote some code for image pixel manipulation. However, I found a strange issue. After loading a image from a local file, I have to add a short "sleep" before loading the image into a canvas, or the previous image (Firefox) / a null will be loaded into the canvas(Edge). Can someone tell me why this happened? Thanks. Code is here:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Image Grey Scale Noise</title>
</head>
<body>
<label for="Selector">Select Image</label>
<br />
<input type="file" id="Selector" multiple=false accept="image/*">
<br />
<br />
<label for="greyScale">Grey Scale</label>
<br />
<input type="range" id="greyScale" min="0.1" max="10" value="4" step="0.1">
<p>Image:</p>
<img id="srcImg" crossOrigin="" src="https://avatars2.githubusercontent.com/u/67770686?s=400&u=24015d89e8fab0fb45cad0be3d282f6062e45b5b&v=4" />
<br />
<img id="outImg" crossOrigin="" src="" />
<script>
const fileSelector = document.getElementById('Selector');
const greyScaleSelector = document.getElementById('greyScale')
const srcImg = document.getElementById('srcImg');
const outImg = document.getElementById('outImg');
var c = document.createElement("Canvas");
var ctx = c.getContext("2d");
async function getNewImage(result, greyScale) {
srcImg.src = result;
await new Promise(r => setTimeout(r, 100)); //Without this, the previous image will be loaded...
c.height = srcImg.height;
c.width = srcImg.width;
ctx.drawImage(srcImg, 0, 0);
var imageData = ctx.getImageData(0, 0, c.width, c.height);
const data = imageData.data
var l = data.length;
for (i = 0; i < l; i += 4) {
var r = Math.random() / greyScale + (1 - 1 / greyScale) //Grey Scale Change
data[i] = Math.min(data[i] * r, 255);
data[i + 1] = Math.min(data[i + 1] * r, 255);
data[i + 2] = Math.min(data[i + 2] * r, 255);
}
ctx.putImageData(imageData, 0, 0);
outImg.src = c.toDataURL("image/png");
}
fileSelector.addEventListener('change', async (event) => {
var greyScale = greyScaleSelector.value;
var result = URL.createObjectURL(fileSelector.files[0]);
await getNewImage(result, greyScale);
URL.revokeObjectURL(result);
})
greyScaleSelector.addEventListener('change', async (event) => {
var greyScale = greyScaleSelector.value;
var result = srcImg.src;
await getNewImage(result, greyScale);
})
</script>
</body>
</html>
Related
I have an image (well data) that is m = 3600 n = 512. It has to stay like this. The data really starts as an array but is then broken down into m,n chunks. I would like to change the aspect ratio in canvas. If I change canvas.width, it gives me more available space that I don't use. If I change canvas.style.width, it increases both height and width.
I want the image to be wider while keeping its same length.
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Font Awesome -->
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.css"
rel="stylesheet"
/>
<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
rel="stylesheet"
/>
<!-- MDB -->
<link
href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/3.11.0/mdb.min.css"
rel="stylesheet"
/>
<!-- MDB -->
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/3.11.0/mdb.min.js"
></script>
<style>
body {
max-width: 910px;
margin: 0 auto;
background-color:#FFFFFF;
text-align:center;
}
</style>
</head>
<body>
<label class="form-label" for="customRange1">X frequency</label>
<div class="range">
<input type="range" class="form-range" id="customRange1" />
</div>
<label class="form-label" for="customRange2">Y frequency</label>
<div class="range">
<input type="range" class="form-range" id="customRange2" />
</div>
<canvas style="outline: black 2px solid;" id="leftcanvas" ></canvas>
<script>
function pixel(array){
var inc = 0;
var buffer = new Uint8ClampedArray(width * height * 4); // have enough bytes
for(var y = 0; y < height; y++) {
for(var x = 0; x < width; x++) {
var pos = (y * width + x) * 4; // position in buffer based on x and y
buffer[pos ] = 0; // some R value [0, 255]
buffer[pos+1] = 0; // some G value
buffer[pos+2] = 0; // some B value
buffer[pos+3] = array[inc]; // set alpha channel
inc++
}
}
return buffer;
};
function makecanvas(idtag,height,imgdata){
var canvas = document.getElementById(idtag),
ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
canvas.height = height;
canvas.width = width;
/* THIS PART I CANT FIGURE OUT */
/* increases canvas but not image */
canvas.width = width*1.25;
/* increases width and length */
//canvas.style.width = 1.25* Math.round(512) + "px";
var idata = ctx.createImageData(width, height);
idata.data.set(imgdata);
ctx.putImageData(idata, 0, 0);
return [idata,ctx];
};
function freq(hzx,hzy){
data = [];
for (var y = 0; y < 3600; y+= 1){
for (var x = 0; x < 512; x+= 1){
data.push(Math.round((255/4*(Math.sin(hzy*2*Math.PI*y/3600) + Math.sin(hzx*2*Math.PI*x/511)+2))))
}
}
return data;
}
trace = freq(1,1)
var width = 512,
height = trace.length/512;
imgmatrix = pixel(trace);
CI = makecanvas("leftcanvas",height,imgmatrix);
var idata = CI[0];
const ictx = CI[1];
var slideX = document.getElementById('customRange1');
var slideY = document.getElementById('customRange2');
slideX.onchange = function() {
hzx = slideX.value;
hzy = slideY.value;
trace = freq(hzx,hzy)
imgmatrix = pixel(trace);
idata.data.set(imgmatrix);
ictx.putImageData(idata, 0, 0);
}
slideY.onchange = function() {
hzx = slideX.value;
hzy = slideY.value;
trace = freq(hzx,hzy)
imgmatrix = pixel(trace);
idata.data.set(imgmatrix);
ictx.putImageData(idata, 0, 0);
}
</script>
</body>
</html>
I'm almost afraid to answer - but what about the most obvious solution:
Instead of just changing the width of the canvas via CSS - change both width & height.
canvas.style.width = 1.2 * Math.round(512) + "px";
canvas.style.height = "3600px";
I can scan the marker as I want to, however now I need my code to be able to scan the markers location and treat it as it is my mouseX and mouseY.
// https://kylemcdonald.github.io/cv-examples/
// more here:
// http://fhtr.org/JSARToolKit/demos/tests/test2.html
var capture;
var w = 640,
h = 480;
var raster, param, pmat, resultMat, detector;
function setup() {
pixelDensity(1); // this makes the internal p5 canvas smaller
capture = createCapture({
audio: false,
video: {
width: w,
height: h
}
}, function() {
console.log('capture ready.')
});
capture.elt.setAttribute('playsinline', '');
createCanvas(w, h);
capture.size(w, h);
capture.hide();
raster = new NyARRgbRaster_Canvas2D(canvas);
param = new FLARParam(canvas.width, canvas.height);
pmat = mat4.identity();
param.copyCameraMatrix(pmat, 100, 10000);
resultMat = new NyARTransMatResult();
detector = new FLARMultiIdMarkerDetector(param, 2);
detector.setContinueMode(true);
}
function draw() {
image(capture, 0, 0, w, h);
canvas.changed = true;
var thresholdAmount = 128; //select('#thresholdAmount').value() * 255 / 100;
detected = detector.detectMarkerLite(raster, thresholdAmount);
select('#markersDetected').elt.innerText = detected;
for (var i = 0; i < detected; i++) {
// read data from the marker
// var id = detector.getIdMarkerData(i);
// get the transformation for this marker
detector.getTransformMatrix(i, resultMat);
// convert the transformation to account for our camera
var mat = resultMat;
var cm = mat4.create();
cm[0] = mat.m00, cm[1] = -mat.m10, cm[2] = mat.m20, cm[3] = 0;
cm[4] = mat.m01, cm[5] = -mat.m11, cm[6] = mat.m21, cm[7] = 0;
cm[8] = -mat.m02, cm[9] = mat.m12, cm[10] = -mat.m22, cm[11] = 0;
cm[12] = mat.m03, cm[13] = -mat.m13, cm[14] = mat.m23, cm[15] = 1;
mat4.multiply(pmat, cm, cm);
// define a set of 3d vertices
var q = 1;
var verts = [
vec4.create(-q, -q, 0, 1),
vec4.create(q, -q, 0, 1),
vec4.create(q, q, 0, 1),
vec4.create(-q, q, 0, 1),
// vec4.create(0, 0, -2*q, 1) // poke up
];
// convert that set of vertices from object space to screen space
var w2 = width / 2,
h2 = height / 2;
verts.forEach(function (v) {
mat4.multiplyVec4(cm, v);
v[0] = v[0] * w2 / v[3] + w2;
v[1] = -v[1] * h2 / v[3] + h2;
});
noStroke();
fill(0, millis() % 255);
beginShape();
verts.forEach(function (v) {
vertex(v[0], v[1]);
});
endShape();
}
}
* {
font-family: sans-serif;
}
<html>
<head>
<meta charset="UTF-8">
<title>MarkerTracking</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js"></script>
<script src="//kig.github.io/JSARToolKit/demos/magi.js"></script>
<script src="//kig.github.io/JSARToolKit/JSARToolKit.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h2>cv-examples Marker Tracking source</h2>
<p>Track an AR marker. Take a picture of one of these markers. Make sure it has a white border.</p>
<!-- <p>Threshold: <input type="range" id="thresholdAmount" value="50"></p>-->
<p>Markers detected: <span id="markersDetected"></span></p>
<script src="sketch.js"></script>
</body>
</html>
The snippet might give an error since it is trying to access the camera to scan the marker.
I have tried creating new variables and starting a brand new detection process which didn't work. I am open to any ideas.
I am trying to add new canvas nodes to the canvas node that is already in the HTML. After the first node is added as a child node, that node just keeps getting replaced with the new node instead of adding after the last node. I have tried using both append and appendChild and both are doing the same thing.
A little context, I am trying to make multiple balls that will "bounce" off when they collide either with each other or the canvas boundary.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<canvas width="550" height="400" style="border: 1px dashed black"></canvas>
<div id="buttons">
<button id="increase">Increase Speed</button>
<button id="decrease">Decrease Speed</button>
<button id="addBall">Add a Ball</button>
</div>
<script src="scripts.js"></script>
</body>
</html>
JavaScript:
const spriteObject = {
sourceX: 0,
sourceY: 0,
sourceWidth: 62,
sourceHeight: 62,
x: 0,
y: 0,
vx: 0,
vy: 0,
width: 30,
height: 30,
radius: 30
};
let canvas = document.querySelector('canvas');
const increase = document.querySelector('#increase');
const decrease = document.querySelector('#decrease');
const addSprite = document.querySelector('#addBall');
const mainCanvas = canvas.getContext('2d');
let drawBall = document.createElement('canvas');
const sprites = []
addSprite.addEventListener('click', () => {
sprites.push(Object.create(spriteObject));
for (let i = 0; i <= sprites.length - 1; i++) {
drawBall.setAttribute('id', i);
drawBall.setAttribute('width', '62');
drawBall.setAttribute('height', '62');
canvas.append(drawBall);
// canvas.appendChild(drawBall);
console.log('append')
const ballDrawingSurface = drawBall.getContext('2d');
ballDrawingSurface.beginPath();
ballDrawingSurface.beginPath();
ballDrawingSurface.translate(sprites[i].x, sprites[i].y);
ballDrawingSurface.arc(sprites[i].width, sprites[i].height, sprites[i].radius, 0, 2 * Math.PI, false);
ballDrawingSurface.stroke();
}
console.log(sprites);
})
requestAnimationFrame(function loop() {
for (let i = 0; i <= sprites.length - 1; i++) {
increase.addEventListener('click', () => {
sprites[i].vx = +5;
});
decrease.addEventListener('click', () => {
sprites[i].vx = -5;
});
//change the ball's position
if (sprites[i].x < 0) {
sprites[i].x = 0;
sprites[i].vx = 5;
}
if (sprites[i].x + sprites[i].sourceWidth > canvas.width) {
sprites[i].x = canvas.width - sprites[i].sourceWidth;
sprites[i].vx = -5;
}
//move the ball
sprites[i].x += sprites[i].vx;
mainCanvas.clearRect(0, 0, canvas.width, canvas.height);
mainCanvas.drawImage(drawBall, sprites[i].sourceX, sprites[i].sourceY,
sprites[i].sourceWidth, sprites[i].sourceHeight,
sprites[i].x, sprites[i].y,
60, 60);
}
requestAnimationFrame(loop)
});
I believe a canvas cannot have child elements, so you do not need to append any children (or do any html manipulation).
I would suggest that you just keep track of your balls in an array (like sprites) and on each animation frame:
Calculate the new position for each ball
Clear the canvas
Draw each ball
When you want to add a ball (for example on click) you can just add a new ball by pushing it to the sprites array.
I'm trying to make a game with canvas but I have a problem. my problem is with the const ctx = canvas.getContext("2d");
I have the error Cannot read property 'getContext' of null
I looked at the other posts nothing helped my problem
(I don't use jquery)
thank you in advance for your future answers <3
Here is my HTML code and JS CODE :
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Human Run</title>
<link href="./style.css" rel="stylesheet">
</head>
<body>
<header>
<h1>Human Run</h1>
<div class="score-container">
<div id="bestScore"></div>
<div class="currentScore"></div>
</div>
</header>
<canvas class="landscape" id="fond" width="350" height="416"></canvas>
<canvas class="ground" id="sol" width="350" height="150"></canvas>
<script src="./script.js" type="text/javascript" ></script>
<script src="./ground.js" type="text/javascript" ></script>
</body>
</html>
JS CODE :
const canvas = document.getElementById('sol');
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = '.media/ground2.png';
// réglages général
const speed = 6.2;
const render = () => {
index++;
// background
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, -((index * (speed / 2)) % canvas.width) + canvas.width, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, -((index * (speed / 2)) % canvas.width), 0, canvas.width, canvas.height);
window.requestAnimationFrame(render);
}
img.onload = render;
Your problem seems to be simply that your javascript loads before your html, as a result, your javascript tries to use an element("sol") that does not exist at the time because it is not there yet. Two solutions either put async on your javascript <script> tag
https://www.w3schools.com/tags/att_script_async.asp
or surround your code with document.addEventListener("DOMContentLoaded",()=>{/* your code goes here*/});
https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event
document.addEventListener("DOMContentLoaded",()=>{
const ctx = sol.getContext("2d");
const img = new Image();
let index = 0;
img.src = 'http://fc04.deviantart.net/fs70/i/2012/014/c/9/rpg_floor_tiles_02_by_neyjour-d4me7dx.jpg';
// réglages général
const speed = 6.2;
const render = () => {
index++;
// background
ctx.drawImage(img, 0, 0, sol.width, sol.height, -((index * (speed / 2)) % sol.width) + sol.width, 0, sol.width, sol.height);
ctx.drawImage(img, 0, 0, sol.width, sol.height, -((index * (speed / 2)) % sol.width), 0, sol.width, sol.height);
window.requestAnimationFrame(render);
}
img.onload = render;
});
<header>
<h1>Human Run</h1>
<div class="score-container">
<div id="bestScore"></div>
<div class="currentScore"></div>
</div>
</header>
<canvas class="landscape" id="fond" width="350" height="416"></canvas>
<canvas class="ground" id="sol" width="350" height="150"></canvas>
<!DOCTYPE html>
<html>
<head>
<title> New Document </title>
<script type="text/javascript">
window.onload = function () {
var area = document.getElementById('area');
alert('area:'+area);
var context = area.getContext('2d');
alert('context:'+context);
if (context) {
var imgd = context.getImageData(0, 0, 500, 300);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
var grayscale = pix[i ] * .3 + pix[i+1] * .59 + pix[i+2] * .11;
pix[i] = grayscale; // red
pix[i+1] = grayscale; // green
pix[i+2] = grayscale; // blue
}
context.putImageData(imgd, 0, 0);
}
};
</script>
</head>
<body>
<canvas id="area" width="500" height="300">
<img id="canvasSource" src="http://www.treehugger.com/elephant-national-heritage-animal-india.jpg" alt="Canvas Source" />
</canvas>
</body>
</html>
You need 2d in lowercase and move the script inside the head tags
area.getContext('2d');
HERE is some code that might work better: http://www.permadi.com/tutorial/jsCanvasGrayscale/index.html
Here is my test html - I do not see an image yet
<!DOCTYPE html>
<html>
<head>
<title> New Document </title>
<script type="text/javascript">
window.onload = function () {
var area = document.getElementById('area');
alert('area:'+area);
var context = area.getContext('2d');
alert('context:'+context)
if (context) {
var imgd = context.getImageData(0, 0, 500, 300);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
var grayscale = pix[i ] * .3 + pix[i+1] * .59 + pix[i+2] * .11;
pix[i] = grayscale; // red
pix[i+1] = grayscale; // green
pix[i+2] = grayscale; // blue
}
context.putImageData(imgd, 0, 0);
}
};
</script>
</head>
<body>
<canvas id="area" width="500" height="300">
<img id="canvasSource" src="http://www.treehugger.com/elephant-national-heritage-animal-india.jpg" alt="Canvas Source" />
</canvas>
</body>
</html>
First, this won't work because you haven't defined context, put this at the start of your code:
var context = document.getElementById('area').getContext('2D');
Second, I don't think you should put an HTML4 doctype when using HTML5.
And third, here is a great tutorial for editing images using canvas, one of the examples is gray-scaling the image: http://www.html5rocks.com/en/tutorials/canvas/imagefilters/
Edit: I forgot something, you want your code to run only after the page has loaded, so you need to put your code in the window.onload event, like this:
window.onload = function () {
//your code
};
Note that you can also do this without any JavaScript at all using SVG filters and a :hover style.
Edit: see http://people.mozilla.org/~roc/filter.xhtml the id="f2" filter and adjust the matrix as desired.