Related
I have a PNG file with dimensions 128x32128 (equivalent to 251 128x128 layers) and when I try the following:
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.SRGB8_ALPHA8, 128, 128, 251)
gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, 128, 128, 251, gl.RGBA, gl.UNSIGNED_BYTE, imageElement)
// imageElement.src = 128x32128.png
I get a browser error reading WebGL: INVALID_VALUE: texSubImage3D: width, height or depth out of range
However, if I try something very similar with another image of dimension 128x8192 (equivalent to 64 layers of 128x128) I get no error:
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.SRGB8_ALPHA8, 128, 128, 32)
gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, 128, 128, 32, gl.RGBA, gl.UNSIGNED_BYTE, imageElement)
// imageElement.src = 128x8192.png
However, if I try the same code with the original image I get the same error:
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.SRGB8_ALPHA8, 128, 128, 32)
gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, 128, 128, 32, gl.RGBA, gl.UNSIGNED_BYTE, imageElement)
// imageElement.src = 128x32128.png
This does not make any sense. Surely this is an implementation bug, as the only thing that changed from example 2 to example 3 was the image, not the parameters to texSubImage3D.
Browser: Chrome v67 on Windows 7 x64
This appears to be a bug in Chrome as it works in Firefox
const ctx = document.createElement('canvas').getContext("2d");
const gl = document.createElement('canvas').getContext("webgl2");
test(1);
test(128);
test(129);
function test(slices) {
log('slices:', slices);
const height = 128 * slices;
ctx.canvas.width = 128;
ctx.canvas.height = height;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(128, height);
ctx.moveTo(128, height);
ctx.lineTo(0, 128);
ctx.stroke();
//document.body.appendChild(ctx.canvas);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex);
log("gl error:", gl.getError());
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, 128, 128, slices);
log("gl error:", gl.getError());
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY, // GLenum target,
0, // GLint level,
0, // GLint xoffset,
0, // GLint yoffset,
0, // GLint zoffset,
128, // GLsizei width,
128, // GLsizei height,
slices, // GLsizei depth,
gl.RGBA, // GLenum format,
gl.UNSIGNED_BYTE, // GLenum type,
ctx.canvas); // TexImageSource source
log("gl error:", gl.getError());
log('.');
}
function log(...args) {
const div = document.createElement('div');
div.textContent = [...args].join(' ');
document.body.appendChild(div);
}
Filed a bug
A 5 by 5 pixel image data is something like this in linearized imagedata array-
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So, the 3x3 pixel data is- 0 0 0 255. How can I get the adjacent pixel positions? Left and right adjacent ones are easy, just minus 4 and plus 4 respectively.
Accessing pixel data
The pixel data from .getImageData().data is a TypedArray of type Uint8ClampedArray. When reading the values they will be in the range 0-255 and in the order Red, Green, Blue, Alpha. If the value of alpha is zero then red, green, and blue will also be zero.
To get the index of a pixel
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
var index = (x + y * imageData.width) * 4;
const red = imageData.data[index];
const green = imageData.data[index + 1];
const blue = imageData.data[index + 2];
const alpha = imageData.data[index + 3];
To move down one pixel
index += imageData.width * 4;
To move up one
index -= imageData.width * 4;
To move left.
index -= 4;
To move right
index += 4;
If you are on the left or right edge and you move in the direction of the edge you will wrap around, on the line above and to the right if moving left and the line below and on the left if moving down.
When setting the image data the values will be floored and clamped to 0-255
imageData.data[index] = 29.5
console.log(imageData.data[index]); // 29
imageData.data[index] = -283
console.log(imageData.data[index]); // 0
imageData.data[index] = 283
console.log(imageData.data[index]); // 255
If you set an index that is outside the array size it will be ignored
imageData.data[-100] = 255;
console.log(imageData.data[-100]); // Undefined
imageData.data[imageData.data.length + 4] = 255;
console.log(imageData.data[imageData.data.length + 4]); // Undefined
You can speed up access and processing by using different array types. For example all of a pixel's channels as one value using Uint32Array
const imageData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
const pixels = new Uint32Array(imageData.data.buffer);
var index32 = x + y * imageData.width; // note there is no 4*;
const pixel = pixels[index32];
The channels are stored in bits 31-24 Alpha, 23-16 Blue, 15-8 Green, 7-0 Red.
You can set a pixel using a hex value
pixels[x + y * imageData.width] = 0xFF0000FF; // red
pixels[x + y * imageData.width] = 0xFF00FF00; // Green
pixels[x + y * imageData.width] = 0xFFFF0000; // Blue
pixels[x + y * imageData.width] = 0xFF000000; // Black
pixels[x + y * imageData.width] = 0xFFFFFFFF; // White
pixels[x + y * imageData.width] = 0; // Transparent
You can set all the pixels in a single call
pixels.fill(0xFF000000); // all pixels black
You can copy array data onto the array with
// set 3 pixels in a row at x,y Red, Yellow, White
pixels.set([0xFF0000FF,0xFF00FFFF,0xFFFFFFFF], x+y * imageData.width);
Warning
If the canvas has any pixel/s that are from an untrusted source it will be tainted and you will not be able to read the pixel data. Trusted sources are same domain or images served with the appropriate CORS header information. Images that are on the file system can not have their pixels accessed. Once a canvas is tainted it can not be cleaned.
A tainted canvas will throw an error when you call ctx.getImageData(0,0,1,1,) MDN does not list this exception for some reason. You will see "SecurityError" DOMException; in the DevTools console and there are plenty of answered question here in StackOverflow on the subject.
You could calculate the index with the width of the matrix and the length of one unit of 4.
The access is zero based.
function getPos(array, x, y, width) {
var p = 4 * (x + y * width);
return array.slice(p, p + 4);
}
var array = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 255, 0, 0, 0, 240, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
// element above
console.log(JSON.stringify(getPos(array, 2, 1, 5))); // [0, 0, 0, 240]
// actual element
console.log(JSON.stringify(getPos(array, 2, 2, 5))); // [0, 0, 0, 255]
// element below
console.log(JSON.stringify(getPos(array, 2, 3, 5))); // [0, 0, 0, 241]
I'm very much an amateur enthusiast trying to make a basic 2d game map with html canvas. I've done this before by using arrays to create div/img tags and position them. I'm now trying to do this with canvas, not with images but simply drawing squares with fillRect().
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map =
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 0, 0]
[1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0];
window.addEventListener("load", function()
{
update();
}, false);
function update()
{
window.requestAnimationFrame(update, canvas);
render();
}
function render()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var row = 0; row < map.length; row++)
{
for(var column = 0; column < map[0].length; column++)
{
switch(map[row][column])
{
case 0:
ctx.fillStyle = #ffffff;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
case 1:
ctx.fillStyle = #009900;
ctx.fillRect
(
row * 64, column * 64, 64, 64
);
break;
}
}
}
}
I'm getting the error: 'Uncaught SyntaxError: Unexpected token ILLEGAL' on line 46 which is 'ctx.fillStyle = #ffffff;'.
I'm a bit stuck as to why it's giving this error, I'm wondering if you can't use the context methods in this way with an array but I can't understand why.
If anyone has any advice, I would be very grateful.
I have a spinning 3D letter "F". For some reason, the "F" is not drawn in the top 20% of the canvas. See this jsFiddle: http://jsfiddle.net/c948jos0/.
The render function:
var rX = 0;
var rY = 0;
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.canvas.width = 400;
gl.canvas.height = 200;
gl.clearColor(0.5,0.5,0.5,1.0);
function render(){
rX += Math.PI/64;
rY += Math.PI/128;
var projection = make2DProjection(gl.canvas.clientWidth,gl.canvas.clientHeight,400);
var translation = makeTranslation(200,50,0);
var rotX = makeXRotation(rX);
var rotY = makeYRotation(rY);
var mat = matrixMultiply(rotX,rotY);
mat = matrixMultiply(mat,translation);
mat = matrixMultiply(mat,projection);
uniforms["u_matrix"](new Float32Array(mat));
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0, 16*6);
setTimeout(render,33);
}
render();
The matrices:
function makeTranslation(tx, ty, tz) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1
];
}
function makeXRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
];
}
function makeYRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
];
}
function make2DProjection(width, height, depth) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1
];
}
I have been following http://webglfundamentals.org/webgl/lessons/webgl-3d-orthographic.html so I figure I must have changed something I shouldn't have or made a typo. The problem is I cant figure out what. I think its the make2DProjection matrix not setting the origin to the top left corner of canvas, but the same code is working in the tutorial so I dont know.
The canvas element is initialized with 300x150 dimensions and so is your viewport.
When changing the canvas size after you acquired the webgl context you also need to set the viewport using
gl.viewport(x, y, width, height)
I've adjusted your fiddle and added a second canvas with an outline so you can see the difference.
Unless you have a specific need to only cover part of the canvas a recommended way to do this is.
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
I'm trying to implement skew transformation using the "x" axis with HTML5 canvas, but my code fails... Here is my JavaScript:
function init() {
var canvas = document.getElementById('skewTest'),
context = canvas.getContext('2d'),
angle = Math.PI / 4;
img = document.createElement('img');
img.src = 'img.gif';
img.onload = function () {
context.setTransform(1, Math.tan(angle), 1, 1, 0, 0);
context.clearRect(0, 0, 200, 200);
context.drawImage(img, 0, 0, 100, 100);
}
}
I'll be very glad if I see working example in JsFiddle.
Thank you in advance!
The correct matrix of skewing in only one direction is
context.setTransform(1, Math.tan(angle), 0, 1, 0, 0);
// ^
With the number at ^ being 1, you are skewing the image in the y-direction by 45° as well.
Sample: http://jsbin.com/etecay/edit#html,live
Canvas can't support direct transform; instead use the below code:
ctx.lineWidth = 100;
ctx.strokeStyle = "rgba(255, 255, 255, 0.5)";
ctx.strokeRect(0, 0, 640, 480);
ctx.lineWidth = 4;
ctx.strokeStyle = "#5E81AB";
ctx.strokeRect(50, 50, 540, 380);