DEV Community

IsaacCodes2021
IsaacCodes2021

Posted on • Edited on

How to generate NFT sets with Node.js

If you've stumbled across this blog and are wondering "What the heck is an NFT" I will point you to this blog that will get you up to speed on NFT's. But for a quick run down, NFT is an acronym for nonfungible token which simply means your token is unique and that uniqueness means tangibly your token is not worth the same amount as the next persons.

The next thing to understand is the process of minting an NFT which can be read about here

NFTs are minted through smart contracts that assign ownership and manage the transferability of the NFT's. When someone creates or mints an NFT, they execute code stored in smart contracts that conform to different standards, such as ERC-721. This information is added to the blockchain where the NFT is being managed. The minting process, from a high level, has the following steps that it goes through:
  • Creating a new block
  • Validating information
  • Recording information into the blockchain
ethereum.net

Before we get started there are a few other things we are going to need:

  • an IDE
  • Node.js
  • a set of images (1000 X 1000px)

There are different processes for setting up your ide and installing node.js so I recommend searching online for the resources based on your machine.

as for the image set you will need at least two different kinds of pictures or 'layers' that will be stacked on to each other so they will all need similarly structured names. ex. layer1_1 layer1_2 ...

Once you have all of the aforementioned, go ahead and continue on.

The Code

the first thing we are going to need to do is create a folder in our computer that will be our directory for our project.
once we have created this folder we will need to run the following in our console IN THIS ORDER:

npm init

npm install canvas

  • npm init creates a configuration file that will set op our project to easily implement node packages
  • canvas is what will be used to generate our images.

after we have done the previous step we can go ahead and write some code and we will start by creating our index.js file and importing some packages into our document

// index.js
const fs = require('fs')
const { createCanvas, loadImage } = require("canvas")
Enter fullscreen mode Exit fullscreen mode

Next we will create a variable canvas and retrieve the context of the canvas. which will be our image area

// index.js
const canvas = createCanvas(1000, 1000)
const ctx = canvas.getContext('2d')
Enter fullscreen mode Exit fullscreen mode

Next we will write a function that will draw something onto our canvas, we will do this with an asychronys function so that the following code will wait for image to be set

// index.js
const drawLayer = async () => {
    const image = await loadImage("./baseImage.png") // <== your file in here
    ctx.drawImage(image, 0, 0, 1000, 1000)
    console.log("this ran")
}
Enter fullscreen mode Exit fullscreen mode

It's important that is the loadimage function you specify YOUR file name that you have for your base file

The next step is to create a function that will save our image that was created when we ran our Draw function.

// index.js
const saveLayer = (_canvas) => {
    fs.writeFileSync("./newImage.png", _canvas.toBuffer("image/png"))
}
Enter fullscreen mode Exit fullscreen mode

whats happening here is we are basically drawing whatever we have coming in as _canvas and saving it as newImage.png
the next step is to add the following line of code in your draw layer function

// index.js
saveLayer(canvas)
Enter fullscreen mode Exit fullscreen mode

now if you call drawLayer() in your index js file and run node index.js in your console you should see a new image appear in your directory. YAY!!

Next we are going to create a new folder in our directory called input which is going to be all the possible input image or layer for our program to grab from and create images from.
inside of that folder your images should be separated by category or 'layer' into different folders

inside of the new input folder create a file called config.js, this file is what is going to make our program more dynamic.
before we get to the fun stuff your going to want to add the following to the top of our new config.js

// config.js
const fs = require('fs')
Enter fullscreen mode Exit fullscreen mode

The first thing we are going to do is create an array of objects that specify the different layers that our output images will have.

// config.js
const dir = `${__dirname}`
const layers = [ // <-- this is what your objects should look like
    {
    id: 1,
    name: "background", //<---------  change this
    location: `${dir}/background/`, // and this 
    elements:  getElements(`${dir}/background/`), // and 
 this accordingly
    postion: {x: 0, y: 0},
    size: {height: 1000, width: 1000}
}
]
Enter fullscreen mode Exit fullscreen mode

the next thing to do is build out the function that we specified above in the elements property

// config.js
const getElements = path => {
  return fs
    .readdirSync(path)
    .filter((item) => !/(^|\/)\.[^\/\.]/g.test(item))
    .map((i, index) => {
      return {
        id: index + 1,
        name: cleanName(i),
        fileName: i,
        rarity: addRarity(i),
      };
    });
};

Enter fullscreen mode Exit fullscreen mode

what this function is doing is reading the path as specified as a parameter and then filtering out bad filenames, then we are going to iterate through each item
and create objects for each of the folders and store them in layers.elements accordingly

next we will define the two functions above cleanName and getRarity.

// config.js
const addRarity = _str => {
  let itemRarity;

  rarity.forEach((r) => {
    if (_str.includes(r.key)) {
      itemRarity = r.val;
    }
  });

  return itemRarity;
};

const cleanName = _str => {
  let name = _str.slice(0, -4);
  rarity.forEach((r) => {
    name = name.replace(r.key, "");
  });
  return name;
};
Enter fullscreen mode Exit fullscreen mode

the clean name function basically produces a clean name without the.png that is creates in the name property of the getElements function
the addRarity function checks if
next we will create the rarity levels that are being iterated through in the cleanName function
your keys and values can be whatever you want them to be but for example:

// config.js
const rarity = [
    {key: "", val: "common"},
    {key: "_r", val: "rare"},
    {key: "_sr", val: "super rare"}
]
Enter fullscreen mode Exit fullscreen mode

You can add or subtract more rarity levels as you please

Now we will export a few things we defined in the config.js file

// config.js
module.exports = {layers, width, height}
Enter fullscreen mode Exit fullscreen mode

and export import them in the index.js file.

// index.js
const {layers, width, height} = require("./input/config.js")
Enter fullscreen mode Exit fullscreen mode

now if you were to add console.log(layers) to your config.js file you should see when you run it an array of objects with the correct number of elements in your folder

next we want to be able to speicify how many versions/editions/NFTs we want to create and we will do that by defining some a variable as a number and running a loop that amount of times

// index.js
const edition; <== set equal to your number of editions
for ( let i=1; i <=1; i++) {
    layers.forEach((layer) => {
        drawLayer(layer)
    })    
}
Enter fullscreen mode Exit fullscreen mode

inside of our loop we are iterating through each of the layers that we imported from our config.js file and drawing a layer with the each layer

next we are going to update our drawLayer function as follows

// index.js
    let element = _layer.elements[Math.floor(Math.random() * _layer.elements.length)] // insert as first line of function
Enter fullscreen mode Exit fullscreen mode

next we will modify the const image and cxt.drawimage to be more dynamic since we will be creating more than one image

// index.js
    const image = await loadImage(`${_layer.location}${element.fileName}`)
    ctx.drawImage(image, _layer.position.x, _layer.position.y, width, height)
Enter fullscreen mode Exit fullscreen mode

now we will add the edition of the image by passing it into our saveLayer function which we call to in our drawLayer function

// index.js
saveLayer(canvas, _edition)
Enter fullscreen mode Exit fullscreen mode

now we set up our saveLayer function to take in the new argument by giving it a parameter where we declared the saveLayer function and make the fucntion more dynamic to handle whatever parameters are getting passed in.

// index.js
const saveLayer = (_canvas, _edition) => {
    fs.writeFileSync(`./output/${_edition}.png`, _canvas.toBuffer("image/png"))
}
Enter fullscreen mode Exit fullscreen mode

resources:

Top comments (0)