使用 Javascript Nodejs 的 NFT 图像生成器(800,000 个加密朋克)
CryptoPunks 是目前最受欢迎的 NFT 项目之一。现在,它们的售价高达数百万美元。没错!太震撼了!世界上只有 10,000 个独一无二的“朋克”。它们每个人都拥有一系列独特的属性,使其与众不同,脱颖而出。
加密朋克的类型和属性
朋克类型
- 外星人
- 猿
- 僵尸
- 女性
- 男性
属性
属性计数
每个朋克可以同时拥有零个属性或最多 7 个属性。
根据给定的材料,我们可以创建超过 800,000 个加密朋克 nft。
让我们抛开一切,编写一个小型 JavaScript 命令行应用程序来生成一堆这样的“朋克”。除此之外,我们还有机会用 JavaScript 解决“多个数组的笛卡尔积”难题。
设置
请在此处下载所有特征层和朋克图像。
我们将使用node-canvas包来绘制此项目的图像。如果遇到问题,请务必遵循安装说明。更多帮助可在此处找到。
npm install canvas
添加导入和配置变量
const fs = require("fs");
const { createCanvas, loadImage } = require("canvas");
const console = require("console");
const imageFormat = {
width: 24,
height: 24
};
// initialize canvas and context in 2d
const canvas = createCanvas(imageFormat.width, imageFormat.height);
const ctx = canvas.getContext("2d");
// some folder directories that we will use throughout the script
const dir = {
traitTypes : `./layers/trait_types`,
outputs: `./outputs`,
background: `./layers/background`,
}
// we will update this total punks in the following steps.
let totalOutputs = 0;
// set the order of layers that you want to print first
const priorities = ['punks','top','beard'];
刷新输出功能
- 创建一个函数来删除输出数据。然后,它会重新创建输出文件夹,并包含新的元数据和 punk 文件夹。
const recreateOutputsDir = () => {
if (fs.existsSync(dir.outputs)) {
fs.rmdirSync(dir.outputs, { recursive: true });
}
fs.mkdirSync(dir.outputs);
fs.mkdirSync(`${dir.outputs}/metadata`);
fs.mkdirSync(`${dir.outputs}/punks`);
};
计算所有可能的结果
在这一步,我们将了解如何从多个特征层数组生成组合。现在,让我们开始吧,享受其中的乐趣。先不要复制粘贴代码。
实现这个所谓的简单功能的方法有很多种。
- 首先是使用 ECMAScript 2019 中引入的 Reduce 和 FlatMap 函数。这是最短的选项,但最容易理解。
const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
- 另一个常见的选择是使用递归函数
const cartesian = (arr) => {
if (arr.length == 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = cartesian (arr.slice(1)); // recur with the rest of array
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
var childArray = [].concat(arr[0][j], allCasesOfRest[i])
result.push(childArray);
}
}
return result;
}
}
大多数选项都需要大量的递归、大量嵌套循环,或者将排列数组存储在内存中。当我们在数百个不同的特征层上运行它们时,情况会变得非常混乱。这些会耗尽你设备的所有内存,最终导致你的电脑/笔记本电脑崩溃。我的电脑就因此被烧坏过好几次。所以,别像我一样。
- 我们可以创建一个函数来计算所有可能的结果,即所有数组长度的乘积,而不是使用递归函数或嵌套循环。
var permsCount = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
permsCount *= arraysToCombine[i].length;
}
- 接下来,我们将设置除数的值来解决数组大小不同
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
}
添加另一个函数,通过根据“n”计算检索字符所需的索引,返回索引“0”和“numPerms - 1”之间的唯一排列
const getPermutation = (n, arraysToCombine) => {
var result = [],
curArray;
for (var i = 0; i < arraysToCombine.length; i++) {
curArray = arraysToCombine[i];
result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
}
return result;
}
接下来我们将使用 for 循环调用 getPermutation (n) 函数
for(var i = 0; i < numPerms; i++) {
combinations.push(getPermutation(i, arraysToCombine));
}
我们需要的完整脚本。
const allPossibleCases = (arraysToCombine) => {
const divisors = [];
let permsCount = 1;
for (let i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
permsCount *= (arraysToCombine[i].length || 1);
}
totalOutputs = permsCount;
const getCombination = (n, arrays, divisors) => arrays.reduce((acc, arr, i) => {
acc.push(arr[Math.floor(n / divisors[i]) % arr.length]);
return acc;
}, []);
const combinations = [];
for (let i = 0; i < permsCount; i++) {
combinations.push(getCombination(i, arraysToCombine, divisors));
}
return combinations;
};
根据这个快速性能测试,最新版本的性能完全超越了其他版本。看起来前景光明!
创建绘制图像函数
const drawImage= async (traitTypes, background, index) => {
// draw background
const backgroundIm = await loadImage(`${dir.background}/${background}`);
ctx.drawImage(backgroundIm,0,0,imageFormat.width,imageFormat.height);
//'N/A': means that this punk doesn't have this trait type
const drawableTraits = traitTypes.filter(x=> x.value !== 'N/A')
// draw all the trait layers for this one punk
for (let index = 0; index < drawableTraits.length; index++) {
const val = drawableTraits[index];
const image = await loadImage(`${dir.traitTypes}/${val.trait_type}/${val.value}`);
ctx.drawImage(image,0,0,imageFormat.width,imageFormat.height);
}
console.log(`Progress: ${index}/ ${totalOutputs}`)
// save metadata
fs.writeFileSync(
`${dir.outputs}/metadata/${index}.json`,
JSON.stringify({
name: `punk ${index}`,
attributes: drawableTraits
}),
function(err){
if(err) throw err;
})
// save image as png file
fs.writeFileSync(
`${dir.outputs}/punks/${index}.png`,
canvas.toBuffer("image/png")
);
}
创建主函数
const main = async () => {
const traitTypesDir = dir.traitTypes;
// register all the traits
const types = fs.readdirSync(traitTypesDir);
// set all prioritised layers which will be drawn first. for eg: punk type, hair and then hat. You can set these values in the priorities array in line 21
const traitTypes = priorities.concat(types.filter(x=> !priorities.includes(x)))
.map(traitType => (
fs.readdirSync(`${traitTypesDir}/${traitType}/`)
.map(value=> {
return {trait_type: traitType, value: value}
}).concat({trait_type: traitType, value: 'N/A'})
));
// register all the backgrounds
const backgrounds = fs.readdirSync(dir.background);
// trait type avail for each punk
const combinations = allPossibleCases(traitTypes)
for (var n = 0; n < combinations.length; n++) {
const randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)]
await drawImage(combinations[n] , randomBackground, n);
}
};
调用输出目录寄存器和主函数
(() => {
recreateOutputsDir();
main();
})();
运行index.js
打开 cmd/powershell 并运行
node index.js
或者
npm build
好了。让应用程序运行并为我们生成所有的 nft。
资源
- 源代码:victorquanlam/cryptopunk-nft-generator
- Stackoverflow:数组值的笛卡尔积
如果您喜欢这篇文章,请点赞。
鏂囩珷鏉ユ簮锛�https://dev.to/victorquanlam/generate-879-120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step-10hp