javascript - putimagedata 绘制像素数据 4 倍 / 不按比例
问题描述
我最近在 twitch 上观看了 Notch 的一些直播,并对他几年前在 Ludum Dare 挑战赛中的一种渲染技术感兴趣。我尝试将他的 java 代码转换为 javascript 并且遇到了一些问题,这是因为我仍然是来自原始创建像素值的 ctx.putimagedata 的新手。
为什么这个应用程序绘制了预期的输出 4 次,而不是缩放到窗口?由于数组的形状,我应该用 4 的乘法或除数进行迭代吗?我很困惑,所以只想在这里发布。我发现的唯一解决方法是,如果我将 this.width 和 this.height 调整为乘以 4,但这是在画布的边界之外绘制,我相信会导致性能变得糟糕,并且不是真正的有效解决方案问题。
有问题的班级:
document.addEventListener('DOMContentLoaded', () => {
//setup
document.body.style.margin = 0;
document.body.style.overflow = `hidden`;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
//global helpers
const randomint = (lower, upper) => {
return Math.floor((Math.random() * upper+1) + lower);
}
const genrandomcolor = () => {
return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1/randomint(1, 2)];
}
class App {
constructor(){
this.scale = 15;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.pixels = [];
this.fov = 10;
this.ub = 0;
this.lr = 0;
this.keys = {
up: false,
down: false,
left: false,
right: false
}
this.speed = 4;
}
update(){
this.keys.up ? this.ub++ : null;
this.keys.down ? this.ub-- : null;
this.keys.left ? this.lr-- : null;
this.keys.right ? this.lr++ : null;
}
draw(){
this.drawspace()
}
drawspace(){
for(let y = 0; y < this.height; y++){
let yd = (y - this.height / 2) / this.height;
yd < 0 ? yd = -yd : null;
const z = this.fov / yd;
for (let x = 0; x < this.width; x++){
let xd = (x - this.width /2) / this.height * z;
const xx = (xd+this.lr*this.speed) & this.scale;
const zz = (z+this.ub*this.speed) & this.scale;
this.pixels[x+y*this.width] = xx * this.scale | zz * this.scale;
}
}
const screen = ctx.createImageData(this.width, this.height);
for (let i = 0; i<this.width*this.height*4; i++){
screen.data[i] = this.pixels[i]
}
ctx.putImageData(screen, 0, 0);
}
}
const app = new App;
window.addEventListener('resize', e => {
canvas.width = app.width = window.innerWidth;
canvas.height = app.height = window.innerHeight;
})
//events
document.addEventListener("keydown", e => {
e.keyCode == 37 ? app.keys.left = true : null;
e.keyCode == 38 ? app.keys.up = true : null;
e.keyCode == 39 ? app.keys.right = true : null;
e.keyCode == 40 ? app.keys.down = true : null;
})
document.addEventListener("keyup", e => {
e.keyCode == 37 ? app.keys.left = false : null;
e.keyCode == 38 ? app.keys.up = false : null;
e.keyCode == 39 ? app.keys.right = false : null;
e.keyCode == 40 ? app.keys.down = false : null;
})
//game loop
const fps = 60;
const interval = 1000 / fps;
let then = Date.now();
let now;
let delta;
const animate = time => {
window.requestAnimationFrame(animate);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval)
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
app.update();
app.draw();
}
}
animate();
});
解决方案
ImageData.data 对象是一个 Uint8ClampedArray,表示每个像素的 4 个红色、绿色、蓝色和 Alpha 通道,每个通道表示为 8 位(值在 0-255 范围内)。
这意味着要设置一个像素,您需要独立设置它的 4 个通道:
const r = data[0];
const g = data[1];
const b = data[2];
const a = data[3];
这表示我们的 ImageData 的第一个像素(左上角的那个)。
因此,为了能够遍历所有像素,我们需要创建一个循环,允许我们从一个像素转到另一个像素。这是通过一次迭代 4 个索引来完成的:
for(
let index = 0;
index < data.length;
index += 4 // increment by 4
) {
const r = data[index + 0];
const g = data[index + 1];
const b = data[index + 2];
const a = data[index + 3];
...
}
现在每个像素都将被遍历,因为它们需要:
//setup
document.body.style.margin = 0;
document.body.style.overflow = `hidden`;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
//global helpers
const randomint = (lower, upper) => {
return Math.floor((Math.random() * upper + 1) + lower);
}
const genrandomcolor = () => {
return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1 / randomint(1, 2)];
}
class App {
constructor() {
this.scale = 15;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.pixels = [];
this.fov = 10;
this.ub = 0;
this.lr = 0;
this.keys = {
up: false,
down: false,
left: false,
right: false
}
this.speed = 4;
}
update() {
this.keys.up ? this.ub++ : null;
this.keys.down ? this.ub-- : null;
this.keys.left ? this.lr-- : null;
this.keys.right ? this.lr++ : null;
}
draw() {
this.drawspace()
}
drawspace() {
for (let y = 0; y < this.height; y++) {
let yd = (y - this.height / 2) / this.height;
yd < 0 ? yd = -yd : null;
const z = this.fov / yd;
for (let x = 0; x < this.width; x++) {
let xd = (x - this.width / 2) / this.height * z;
const xx = (xd + this.lr * this.speed) & this.scale;
const zz = (z + this.ub * this.speed) & this.scale;
this.pixels[x + y * this.width] = xx * this.scale | zz * this.scale;
}
}
const screen = ctx.createImageData(this.width, this.height);
for (let i = 0, j=0; i < screen.data.length; i += 4) {
j++; // so we can iterate through this.pixels
screen.data[i] = this.pixels[j]; // r
screen.data[i + 1] = this.pixels[j], // g
screen.data[i + 2] = this.pixels[j] // b
screen.data[i + 3] = 255; // full opacity
}
ctx.putImageData(screen, 0, 0);
}
}
const app = new App;
window.addEventListener('resize', e => {
canvas.width = app.width = window.innerWidth;
canvas.height = app.height = window.innerHeight;
})
//events
document.addEventListener("keydown", e => {
e.keyCode == 37 ? app.keys.left = true : null;
e.keyCode == 38 ? app.keys.up = true : null;
e.keyCode == 39 ? app.keys.right = true : null;
e.keyCode == 40 ? app.keys.down = true : null;
})
document.addEventListener("keyup", e => {
e.keyCode == 37 ? app.keys.left = false : null;
e.keyCode == 38 ? app.keys.up = false : null;
e.keyCode == 39 ? app.keys.right = false : null;
e.keyCode == 40 ? app.keys.down = false : null;
})
//game loop
const fps = 60;
const interval = 1000 / fps;
let then = Date.now();
let now;
let delta;
const animate = time => {
window.requestAnimationFrame(animate);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval)
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
app.update();
app.draw();
}
}
animate();
但请注意,您也可以在 ArrayBuffer 上使用其他视图,并将每个像素直接作为 32 位值处理:
//setup
document.body.style.margin = 0;
document.body.style.overflow = `hidden`;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
//global helpers
const randomint = (lower, upper) => {
return Math.floor((Math.random() * upper + 1) + lower);
}
const genrandomcolor = () => {
return [randomint(0, 255), randomint(0, 255), randomint(0, 255), 1 / randomint(1, 2)];
}
class App {
constructor() {
this.scale = 15;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.pixels = [];
this.fov = 10;
this.ub = 0;
this.lr = 0;
this.keys = {
up: false,
down: false,
left: false,
right: false
}
this.speed = 4;
}
update() {
this.keys.up ? this.ub++ : null;
this.keys.down ? this.ub-- : null;
this.keys.left ? this.lr-- : null;
this.keys.right ? this.lr++ : null;
}
draw() {
this.drawspace()
}
drawspace() {
for (let y = 0; y < this.height; y++) {
let yd = (y - this.height / 2) / this.height;
yd < 0 ? yd = -yd : null;
const z = this.fov / yd;
for (let x = 0; x < this.width; x++) {
let xd = (x - this.width / 2) / this.height * z;
const xx = (xd + this.lr * this.speed) & this.scale;
const zz = (z + this.ub * this.speed) & this.scale;
this.pixels[x + y * this.width] = xx * this.scale | zz * this.scale;
}
}
const screen = ctx.createImageData(this.width, this.height);
// use a 32bits view
const data = new Uint32Array(screen.data.buffer);
for (let i = 0, j=0; i < this.width * this.height; i ++) {
// values are 0-255 range, we convert this to 0xFFnnnnnn 32bits
data[i] = (this.pixels[i] / 255 * 0xFFFFFF) + 0xFF000000;
}
ctx.putImageData(screen, 0, 0);
}
}
const app = new App;
window.addEventListener('resize', e => {
canvas.width = app.width = window.innerWidth;
canvas.height = app.height = window.innerHeight;
})
//events
document.addEventListener("keydown", e => {
e.keyCode == 37 ? app.keys.left = true : null;
e.keyCode == 38 ? app.keys.up = true : null;
e.keyCode == 39 ? app.keys.right = true : null;
e.keyCode == 40 ? app.keys.down = true : null;
})
document.addEventListener("keyup", e => {
e.keyCode == 37 ? app.keys.left = false : null;
e.keyCode == 38 ? app.keys.up = false : null;
e.keyCode == 39 ? app.keys.right = false : null;
e.keyCode == 40 ? app.keys.down = false : null;
})
//game loop
const fps = 60;
const interval = 1000 / fps;
let then = Date.now();
let now;
let delta;
const animate = time => {
window.requestAnimationFrame(animate);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval)
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
app.update();
app.draw();
}
}
animate();
推荐阅读
- websocket - 是否可以将 websocket 消息发送到 kafka 主题?
- amazon-web-services - 无法访问使用现有实例的映像创建的 AWS 实例
- java - JSch 没有检测到 $HOME/.ssh/id_rsa 公钥
- angular - 如何在 ionic 4 中使用 ion-range 调整所有页面的字体大小?
- latex - 更改乳胶中的引用语言
- ssl - openssl 验证返回码 7(证书签名失败)
- javascript - 如何在javascript中将对象数组过滤为另一个对象数组?
- python - 在 Python 的日期范围内为每年创建新行?
- go - 有没有办法从地图中删除第一个元素?
- jenkins - 詹金斯:声明性管道中的未知阶段部分“矩阵”