使用 Javascript Nodejs 的 NFT 图像生成器(800,000 个加密朋克)

2025-06-10

使用 Javascript Nodejs 的 NFT 图像生成器(800,000 个加密朋克)

CryptoPunks 是目前最受欢迎的 NFT 项目之一。现在,它们的售价高达数百万美元。没错!太震撼了!世界上只有 10,000 个独一无二的“朋克”。它们每个人都拥有一系列独特的属性,使其与众不同,脱颖而出。

加密朋克的类型和属性

朋克类型

  1. 外星人
  2. 僵尸
  3. 女性
  4. 男性

属性

每种朋克类型大约有 89 种属性。
朋克属性

属性计数

每个朋克可以同时拥有零个属性或最多 7 个属性。

根据给定的材料,我们可以创建超过 800,000 个加密朋克 nft。


让我们抛开一切,编写一个小型 JavaScript 命令行应用程序来生成一堆这样的“朋克”。除此之外,我们还有机会用 JavaScript 解决“多个数组的笛卡尔积”难题。

设置

请在此处下载所有特征层和朋克图像

文件夹结构:
文件夹结构

我们将使用node-canvas包来绘制此项目的图像。如果遇到问题,请务必遵循安装说明。更多帮助可在此处找到。

npm install canvas
Enter fullscreen mode Exit fullscreen mode

添加导入和配置变量

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'];
Enter fullscreen mode Exit fullscreen mode

刷新输出功能

  • 创建一个函数来删除输出数据。然后,它会重新创建输出文件夹,并包含新的元数据和 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`);

};
Enter fullscreen mode Exit fullscreen mode

计算所有可能的结果

在这一步,我们将了解如何从多个特征层数组生成组合。现在,让我们开始吧,享受其中的乐趣。先不要复制粘贴代码。

实现这个所谓的简单功能的方法有很多种。

  • 首先是使用 ECMAScript 2019 中引入的 Reduce 和 FlatMap 函数。这是最短的选项,但最容易理解。
const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Enter fullscreen mode Exit fullscreen mode
  • 另一个常见的选择是使用递归函数
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;
  }
}
Enter fullscreen mode Exit fullscreen mode

大多数选项都需要大量的递归、大量嵌套循环,或者将排列数组存储在内存中。当我们在数百个不同的特征层上运行它们时,情况会变得非常混乱。这些会耗尽你设备的所有内存,最终导致你的电脑/笔记本电脑崩溃。我的电脑就因此被烧坏过好几次。所以,别像我一样。

  • 我们可以创建一个函数来计算所有可能的结果,即所有数组长度的乘积,而不是使用递归函数或嵌套循环。
var permsCount = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
    permsCount *= arraysToCombine[i].length;
}
Enter fullscreen mode Exit fullscreen mode
  • 接下来,我们将设置除数的值来解决数组大小不同
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
      divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
   }
Enter fullscreen mode Exit fullscreen mode

添加另一个函数,通过根据“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;
}
Enter fullscreen mode Exit fullscreen mode

接下来我们将使用 for 循环调用 getPermutation (n) 函数

    for(var i = 0; i < numPerms; i++) {
        combinations.push(getPermutation(i, arraysToCombine));
    }
Enter fullscreen mode Exit fullscreen mode

我们需要的完整脚本。

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;

};
Enter fullscreen mode Exit fullscreen mode

根据这个快速性能测试,最新版本的性能完全超越了其他版本。看起来前景光明!

性能测试

创建绘制图像函数

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")
        );

}
Enter fullscreen mode Exit fullscreen mode

创建主函数

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);
    }
};
Enter fullscreen mode Exit fullscreen mode

调用输出目录寄存器和主函数

(() => {

    recreateOutputsDir();

    main();

})();
Enter fullscreen mode Exit fullscreen mode

运行index.js

打开 cmd/powershell 并运行

node index.js
Enter fullscreen mode Exit fullscreen mode

或者

npm build
Enter fullscreen mode Exit fullscreen mode

好了。让应用程序运行并为我们生成所有的 nft。

资源

  1. 源代码:victorquanlam/cryptopunk-nft-generator
  2. Stackoverflow:数组值的笛卡尔积

如果您喜欢这篇文章,请点赞。

鏂囩珷鏉ユ簮锛�https://dev.to/victorquanlam/generate-879-120-cryptopunk-nfts-with-javascript-nodejs-command-line-app-step-by-step-10hp
PREV
如何免费构建 WebRTC React Native 应用
NEXT
用户和应用程序如何在互联网上保持安全:代理服务器始终处于关闭状态