使用 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 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com
          
