优化策略 javascript
Strategy to optimize javascript
我编写了一个 javascript 程序,它使用遗传算法仅使用三角形重新创建图像。策略如下:
- 生成一个随机的模型池,每个模型都有一个三角形数组(3 个点和一种颜色)
- 评估每个模型的适应度。为此,我将原始图像的像素阵列与我的模型进行比较。我使用 Cosine Similarity 来比较数组
- 保留最好的模型,并将它们配对以创建新模型
- 随机改变一些模型
- 评估新池并继续
经过一些迭代后效果很好,如您所见:
我遇到的问题是它非常慢,大部分时间都花在了获取模型的像素上(将三角形列表(颜色+点)转换为像素数组)。
以下是我现在的做法:
我的像素数组是一维数组,我需要能够将 x,y 坐标转换为索引:
static getIndex(x, y, width) {
return 4 * (width * y + x);
}
然后我可以画一个点:
static plot(x, y, color, img) {
let idx = this.getIndex(x, y, img.width);
let added = [color.r, color.g, color.b, map(color.a, 0, 255, 0, 1)];
let base = [img.pixels[idx], img.pixels[idx + 1], img.pixels[idx + 2], map(img.pixels[idx + 3], 0, 255, 0, 1)];
let a01 = 1 - (1 - added[3]) * (1 - base[3]);
img.pixels[idx + 0] = Math.round((added[0] * added[3] / a01) + (base[0] * base[3] * (1 - added[3]) / a01)); // red
img.pixels[idx + 1] = Math.round((added[1] * added[3] / a01) + (base[1] * base[3] * (1 - added[3]) / a01)); // green
img.pixels[idx + 2] = Math.round((added[2] * added[3] / a01) + (base[2] * base[3] * (1 - added[3]) / a01)); // blue
img.pixels[idx + 3] = Math.round(map(a01, 0, 1, 0, 255));
}
然后一行:
static line(x0, y0, x1, y1, img, color) {
x0 = Math.round(x0);
y0 = Math.round(y0);
x1 = Math.round(x1);
y1 = Math.round(y1);
let dx = Math.abs(x1 - x0);
let dy = Math.abs(y1 - y0);
let sx = x0 < x1 ? 1 : -1;
let sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
do {
this.plot(x0, y0, color, img);
let e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
} while (x0 != x1 || y0 != y1);
}
最后,一个三角形:
static drawTriangle(triangle, img) {
for (let i = 0; i < triangle.points.length; i++) {
let point = triangle.points[i];
let p1 =
i === triangle.points.length - 1
? triangle.points[0]
: triangle.points[i + 1];
this.line(point.x, point.y, p1.x, p1.y, img, triangle.color);
}
this.fillTriangle(triangle, img);
}
static fillTriangle(triangle, img) {
let vertices = Array.from(triangle.points);
vertices.sort((a, b) => a.y > b.y);
if (vertices[1].y == vertices[2].y) {
this.fillBottomFlatTriangle(vertices[0], vertices[1], vertices[2], img, triangle.color);
} else if (vertices[0].y == vertices[1].y) {
this.fillTopFlatTriangle(vertices[0], vertices[1], vertices[2], img, triangle.color);
} else {
let v4 = {
x: vertices[0].x + float(vertices[1].y - vertices[0].y) / float(vertices[2].y - vertices[0].y) * (vertices[2].x - vertices[0].x),
y: vertices[1].y
};
this.fillBottomFlatTriangle(vertices[0], vertices[1], v4, img, triangle.color);
this.fillTopFlatTriangle(vertices[1], v4, vertices[2], img, triangle.color);
}
}
static fillBottomFlatTriangle(v1, v2, v3, img, color) {
let invslope1 = (v2.x - v1.x) / (v2.y - v1.y);
let invslope2 = (v3.x - v1.x) / (v3.y - v1.y);
let curx1 = v1.x;
let curx2 = v1.x;
for (let scanlineY = v1.y; scanlineY <= v2.y; scanlineY++) {
this.line(curx1, scanlineY, curx2, scanlineY, img, color);
curx1 += invslope1;
curx2 += invslope2;
}
}
static fillTopFlatTriangle(v1, v2, v3, img, color) {
let invslope1 = (v3.x - v1.x) / (v3.y - v1.y);
let invslope2 = (v3.x - v2.x) / (v3.y - v2.y);
let curx1 = v3.x;
let curx2 = v3.x;
for (let scanlineY = v3.y; scanlineY > v1.y; scanlineY--) {
this.line(curx1, scanlineY, curx2, scanlineY, img, color);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
您可以查看完整的代码here
所以,我想知道:
- 是否可以优化此代码?
- 如果是,最好的方法是什么?也许有一个图书馆比我做得更好?或者使用工人?
谢谢!
我已经测试了你的建议,结果如下:
- 使用 RMS 而不是余弦相似度:我不确定相似度的度量是否更好,但绝对不会更差。好像运行也快了一点。
- 使用 UInt8Array:它肯定有影响,但不会 运行 快很多。不过不慢。
- 绘制到隐形 canvas:绝对更快更容易!我可以删除我所有的绘图函数并用几行代码替换它,而且 运行 快了很多!
这是绘制到不可见 canvas 的代码:
var canvas = document.createElement('canvas');
canvas.id = "CursorLayer";
canvas.width = this.width;
canvas.height = this.height;
canvas.display = "none";
var body = document.getElementsByTagName("body")[0];
body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.fillRect(0, 0, this.width, this.height);
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
ctx.fillStyle = "rgba(" +item.color.r + ','+item.color.g+','+item.color.b+','+map(item.color.a, 0, 255, 0, 1)+")";
ctx.beginPath();
ctx.moveTo(item.points[0].x, item.points[0].y);
ctx.lineTo(item.points[1].x, item.points[1].y);
ctx.lineTo(item.points[2].x, item.points[2].y);
ctx.fill();
}
let pixels = ctx.getImageData(0, 0, this.width, this.height).data;
//delete canvas
body.removeChild(canvas);
return pixels;
在这些更改之前,我的代码 运行ning 大约每秒迭代 1.68 次。
现在它 运行s 大约每秒 16.45 次迭代!
查看完整代码here。
再次感谢!
我编写了一个 javascript 程序,它使用遗传算法仅使用三角形重新创建图像。策略如下:
- 生成一个随机的模型池,每个模型都有一个三角形数组(3 个点和一种颜色)
- 评估每个模型的适应度。为此,我将原始图像的像素阵列与我的模型进行比较。我使用 Cosine Similarity 来比较数组
- 保留最好的模型,并将它们配对以创建新模型
- 随机改变一些模型
- 评估新池并继续
经过一些迭代后效果很好,如您所见:
我遇到的问题是它非常慢,大部分时间都花在了获取模型的像素上(将三角形列表(颜色+点)转换为像素数组)。 以下是我现在的做法:
我的像素数组是一维数组,我需要能够将 x,y 坐标转换为索引:
static getIndex(x, y, width) {
return 4 * (width * y + x);
}
然后我可以画一个点:
static plot(x, y, color, img) {
let idx = this.getIndex(x, y, img.width);
let added = [color.r, color.g, color.b, map(color.a, 0, 255, 0, 1)];
let base = [img.pixels[idx], img.pixels[idx + 1], img.pixels[idx + 2], map(img.pixels[idx + 3], 0, 255, 0, 1)];
let a01 = 1 - (1 - added[3]) * (1 - base[3]);
img.pixels[idx + 0] = Math.round((added[0] * added[3] / a01) + (base[0] * base[3] * (1 - added[3]) / a01)); // red
img.pixels[idx + 1] = Math.round((added[1] * added[3] / a01) + (base[1] * base[3] * (1 - added[3]) / a01)); // green
img.pixels[idx + 2] = Math.round((added[2] * added[3] / a01) + (base[2] * base[3] * (1 - added[3]) / a01)); // blue
img.pixels[idx + 3] = Math.round(map(a01, 0, 1, 0, 255));
}
然后一行:
static line(x0, y0, x1, y1, img, color) {
x0 = Math.round(x0);
y0 = Math.round(y0);
x1 = Math.round(x1);
y1 = Math.round(y1);
let dx = Math.abs(x1 - x0);
let dy = Math.abs(y1 - y0);
let sx = x0 < x1 ? 1 : -1;
let sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
do {
this.plot(x0, y0, color, img);
let e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
} while (x0 != x1 || y0 != y1);
}
最后,一个三角形:
static drawTriangle(triangle, img) {
for (let i = 0; i < triangle.points.length; i++) {
let point = triangle.points[i];
let p1 =
i === triangle.points.length - 1
? triangle.points[0]
: triangle.points[i + 1];
this.line(point.x, point.y, p1.x, p1.y, img, triangle.color);
}
this.fillTriangle(triangle, img);
}
static fillTriangle(triangle, img) {
let vertices = Array.from(triangle.points);
vertices.sort((a, b) => a.y > b.y);
if (vertices[1].y == vertices[2].y) {
this.fillBottomFlatTriangle(vertices[0], vertices[1], vertices[2], img, triangle.color);
} else if (vertices[0].y == vertices[1].y) {
this.fillTopFlatTriangle(vertices[0], vertices[1], vertices[2], img, triangle.color);
} else {
let v4 = {
x: vertices[0].x + float(vertices[1].y - vertices[0].y) / float(vertices[2].y - vertices[0].y) * (vertices[2].x - vertices[0].x),
y: vertices[1].y
};
this.fillBottomFlatTriangle(vertices[0], vertices[1], v4, img, triangle.color);
this.fillTopFlatTriangle(vertices[1], v4, vertices[2], img, triangle.color);
}
}
static fillBottomFlatTriangle(v1, v2, v3, img, color) {
let invslope1 = (v2.x - v1.x) / (v2.y - v1.y);
let invslope2 = (v3.x - v1.x) / (v3.y - v1.y);
let curx1 = v1.x;
let curx2 = v1.x;
for (let scanlineY = v1.y; scanlineY <= v2.y; scanlineY++) {
this.line(curx1, scanlineY, curx2, scanlineY, img, color);
curx1 += invslope1;
curx2 += invslope2;
}
}
static fillTopFlatTriangle(v1, v2, v3, img, color) {
let invslope1 = (v3.x - v1.x) / (v3.y - v1.y);
let invslope2 = (v3.x - v2.x) / (v3.y - v2.y);
let curx1 = v3.x;
let curx2 = v3.x;
for (let scanlineY = v3.y; scanlineY > v1.y; scanlineY--) {
this.line(curx1, scanlineY, curx2, scanlineY, img, color);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
您可以查看完整的代码here
所以,我想知道:
- 是否可以优化此代码?
- 如果是,最好的方法是什么?也许有一个图书馆比我做得更好?或者使用工人?
谢谢!
我已经测试了你的建议,结果如下:
- 使用 RMS 而不是余弦相似度:我不确定相似度的度量是否更好,但绝对不会更差。好像运行也快了一点。
- 使用 UInt8Array:它肯定有影响,但不会 运行 快很多。不过不慢。
- 绘制到隐形 canvas:绝对更快更容易!我可以删除我所有的绘图函数并用几行代码替换它,而且 运行 快了很多!
这是绘制到不可见 canvas 的代码:
var canvas = document.createElement('canvas');
canvas.id = "CursorLayer";
canvas.width = this.width;
canvas.height = this.height;
canvas.display = "none";
var body = document.getElementsByTagName("body")[0];
body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.fillRect(0, 0, this.width, this.height);
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
ctx.fillStyle = "rgba(" +item.color.r + ','+item.color.g+','+item.color.b+','+map(item.color.a, 0, 255, 0, 1)+")";
ctx.beginPath();
ctx.moveTo(item.points[0].x, item.points[0].y);
ctx.lineTo(item.points[1].x, item.points[1].y);
ctx.lineTo(item.points[2].x, item.points[2].y);
ctx.fill();
}
let pixels = ctx.getImageData(0, 0, this.width, this.height).data;
//delete canvas
body.removeChild(canvas);
return pixels;
在这些更改之前,我的代码 运行ning 大约每秒迭代 1.68 次。 现在它 运行s 大约每秒 16.45 次迭代!
查看完整代码here。
再次感谢!