Skip to content

Commit 2d74294

Browse files
authored
Merge pull request #9 from caiaga/main
adding readme, logs, some improvements
2 parents 3e3c9a7 + 4759818 commit 2d74294

File tree

3 files changed

+143
-56
lines changed

3 files changed

+143
-56
lines changed

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,36 @@
11
# generative-art-opensource
22
Create generative art by using the canvas api and node js, feel free to contribute to this repo with new ideas.
3+
4+
# Project Setup
5+
- install `node.js` on your local system (https://nodejs.org/en/)
6+
- clone the repository to your local system `[email protected]:HashLips/generative-art-opensource.git`
7+
- run `yarn add all` to install dependencies
8+
9+
# How to use
10+
## Run the code
11+
1. Run `node index.js`
12+
2. Open the `./output` folder to find your generated images to use as NFTs
13+
14+
## Adjust the provided configuration and resources
15+
### Configuration file
16+
The file `./input/config.js` contains the following properties that can be adjusted to your preference in order to change the behavior of the NFT generation procedure:
17+
- width: - of your image in pixels. Default: `1000px`
18+
- height: - of your image in pixels. Default: `1000px`
19+
- dir: - where image parts are stored. Default: `./input`
20+
- description: - of your generated NFT
21+
- baseImageUri: - URL base to access your NFTs from. This will be used by platforms to find your image resource. This expects the image to be accessible by it's id like `${baseImageUri}/${id}`.
22+
- startEditionFrom: - number (int) to start naming NFTs from.
23+
- editionSize: - number (int) to end edition at.
24+
- rarityWeights: - allows to provide rarity categories and how many of each type to include in an edition.
25+
- layers: list of layers that should be used to render the image. See next section for detail.
26+
27+
### Image layers
28+
The image layers are different parts that make up a full image by overlaying on top of each other. E.g. in this example we start with the eyeball and layer features like the eye lids or iris on top to create the completed and unique eye, which we can then use as part of our NFT collection.
29+
To ensure uniqueness, we want to add various features and multiple options for each of them in order to allow enough permutations for the amount of unique images we require.
30+
31+
To start, copy the layers/features and their images in a flat hierarchy at a directory of your choice (by default we expect them in `./input/`). The features should contain options for each rarity that is provided via the config file.
32+
33+
After adding the layers, adjust them accordingly in the `config.js` by providing the directory path, positioning and sizes.
34+
35+
# Development suggestions
36+
- Preferably use VSCode with the prettifier plugin for a consistent coding style (or equivalent js formatting rules)

index.js

+109-54
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,21 @@ const {
88
baseImageUri,
99
editionSize,
1010
startEditionFrom,
11-
endEditionAt,
1211
rarityWeights,
1312
} = require("./input/config.js");
1413
const console = require("console");
1514
const canvas = createCanvas(width, height);
1615
const ctx = canvas.getContext("2d");
17-
var metadataList = [];
18-
var attributesList = [];
19-
var dnaList = [];
2016

17+
// saves the generated image to the output folder, using the edition count as the name
2118
const saveImage = (_editionCount) => {
2219
fs.writeFileSync(
2320
`./output/${_editionCount}.png`,
2421
canvas.toBuffer("image/png")
2522
);
2623
};
2724

25+
// adds a signature to the top left corner of the canvas
2826
const signImage = (_sig) => {
2927
ctx.fillStyle = "#000000";
3028
ctx.font = "bold 30pt Courier";
@@ -33,6 +31,7 @@ const signImage = (_sig) => {
3331
ctx.fillText(_sig, 40, 40);
3432
};
3533

34+
// generate a random color hue
3635
const genColor = () => {
3736
let hue = Math.floor(Math.random() * 360);
3837
let pastel = `hsl(${hue}, 100%, 85%)`;
@@ -44,7 +43,8 @@ const drawBackground = () => {
4443
ctx.fillRect(0, 0, width, height);
4544
};
4645

47-
const addMetadata = (_dna, _edition) => {
46+
// add metadata for individual nft edition
47+
const generateMetadata = (_dna, _edition, _attributesList) => {
4848
let dateTime = Date.now();
4949
let tempMetadata = {
5050
dna: _dna.join(""),
@@ -53,20 +53,23 @@ const addMetadata = (_dna, _edition) => {
5353
image: `${baseImageUri}/${_edition}`,
5454
edition: _edition,
5555
date: dateTime,
56-
attributes: attributesList,
56+
attributes: _attributesList,
5757
};
58-
metadataList.push(tempMetadata);
59-
attributesList = [];
58+
return tempMetadata;
6059
};
6160

62-
const addAttributes = (_element) => {
61+
// prepare attributes for the given element to be used as metadata
62+
const getAttributeForElement = (_element) => {
6363
let selectedElement = _element.layer.selectedElement;
64-
attributesList.push({
64+
let attribute = {
6565
name: selectedElement.name,
6666
rarity: selectedElement.rarity,
67-
});
67+
};
68+
return attribute;
6869
};
6970

71+
// loads an image from the layer path
72+
// returns the image in a format usable by canvas
7073
const loadLayerImg = async (_layer) => {
7174
return new Promise(async (resolve) => {
7275
const image = await loadImage(`${_layer.selectedElement.path}`);
@@ -82,9 +85,11 @@ const drawElement = (_element) => {
8285
_element.layer.size.width,
8386
_element.layer.size.height
8487
);
85-
addAttributes(_element);
8688
};
8789

90+
// check the configured layer to find information required for rendering the layer
91+
// this maps the layer information to the generated dna and prepares it for
92+
// drawing on a canvas
8893
const constructLayerToDna = (_dna = [], _layers = [], _rarity) => {
8994
let mappedDnaToLayers = _layers.map((layer, index) => {
9095
let selectedElement = layer.elements[_rarity][_dna[index]];
@@ -99,24 +104,15 @@ const constructLayerToDna = (_dna = [], _layers = [], _rarity) => {
99104
return mappedDnaToLayers;
100105
};
101106

102-
const getRarity = (_editionCount) => {
103-
let rarity = "";
104-
rarityWeights.forEach((rarityWeight) => {
105-
if (
106-
_editionCount >= rarityWeight.from &&
107-
_editionCount <= rarityWeight.to
108-
) {
109-
rarity = rarityWeight.value;
110-
}
111-
});
112-
return rarity;
113-
};
114-
107+
// check if the given dna is contained within the given dnaList
108+
// return true if it is, indicating that this dna is already in use and should be recalculated
115109
const isDnaUnique = (_DnaList = [], _dna = []) => {
116110
let foundDna = _DnaList.find((i) => i.join("") === _dna.join(""));
117111
return foundDna == undefined ? true : false;
118112
};
119113

114+
// create a dna based on the available layers for the given rarity
115+
// use a random part for each layer
120116
const createDna = (_layers, _rarity) => {
121117
let randNum = [];
122118
_layers.forEach((layer) => {
@@ -126,48 +122,107 @@ const createDna = (_layers, _rarity) => {
126122
return randNum;
127123
};
128124

125+
// holds which rarity should be used for which image in edition
126+
let rarityForEdition;
127+
// get the rarity for the image by edition number that should be generated
128+
const getRarity = (_editionCount) => {
129+
if (!rarityForEdition) {
130+
// prepare array to iterate over
131+
rarityForEdition = [];
132+
rarityWeights.forEach((rarityWeight) => {
133+
for (let i = rarityWeight.from; i <= rarityWeight.to; i++) {
134+
rarityForEdition.push(rarityWeight.value);
135+
}
136+
});
137+
}
138+
return rarityForEdition[editionSize - _editionCount];
139+
};
140+
129141
const writeMetaData = (_data) => {
130142
fs.writeFileSync("./output/_metadata.json", _data);
131143
};
132144

145+
// holds which dna has already been used during generation
146+
let dnaListByRarity = {};
147+
// holds metadata for all NFTs
148+
let metadataList = [];
149+
// Create generative art by using the canvas api
133150
const startCreating = async () => {
151+
console.log('##################');
152+
console.log('# Generative Art');
153+
console.log('# - Create your NFT collection');
154+
console.log('##################');
155+
156+
console.log();
157+
console.log('start creating NFTs.')
158+
159+
// clear meta data from previous run
134160
writeMetaData("");
161+
162+
// prepare dnaList object
163+
rarityWeights.forEach((rarityWeight) => {
164+
dnaListByRarity[rarityWeight.value] = [];
165+
});
166+
167+
// create NFTs from startEditionFrom to editionSize
135168
let editionCount = startEditionFrom;
136-
while (editionCount <= endEditionAt) {
137-
console.log(editionCount);
169+
while (editionCount <= editionSize) {
170+
console.log('-----------------')
171+
console.log('creating NFT %d of %d', editionCount, editionSize);
138172

173+
// get rarity from to config to create NFT as
139174
let rarity = getRarity(editionCount);
140-
console.log(rarity);
175+
console.log('- rarity: ' + rarity);
141176

177+
// calculate the NFT dna by getting a random part for each layer/feature
178+
// based on the ones available for the given rarity to use during generation
142179
let newDna = createDna(layers, rarity);
143-
console.log(dnaList);
144-
145-
if (isDnaUnique(dnaList, newDna)) {
146-
let results = constructLayerToDna(newDna, layers, rarity);
147-
let loadedElements = []; //promise array
148-
149-
results.forEach((layer) => {
150-
loadedElements.push(loadLayerImg(layer));
151-
});
152-
153-
await Promise.all(loadedElements).then((elementArray) => {
154-
ctx.clearRect(0, 0, width, height);
155-
drawBackground();
156-
elementArray.forEach((element) => {
157-
drawElement(element);
158-
});
159-
signImage(`#${editionCount}`);
160-
saveImage(editionCount);
161-
addMetadata(newDna, editionCount);
162-
console.log(`Created edition: ${editionCount} with DNA: ${newDna}`);
163-
});
164-
dnaList.push(newDna);
165-
editionCount++;
166-
} else {
167-
console.log("DNA exists!");
180+
while (!isDnaUnique(dnaListByRarity[rarity], newDna)) {
181+
// recalculate dna as this has been used before.
182+
console.log('found duplicate DNA ' + newDna.join('-') + ', recalculate...');
183+
newDna = createDna(layers, rarity);
168184
}
185+
console.log('- dna: ' + newDna.join('-'));
186+
187+
// propagate information about required layer contained within config into a mapping object
188+
// = prepare for drawing
189+
let results = constructLayerToDna(newDna, layers, rarity);
190+
let loadedElements = [];
191+
192+
// load all images to be used by canvas
193+
results.forEach((layer) => {
194+
loadedElements.push(loadLayerImg(layer));
195+
});
196+
197+
// elements are loaded asynchronously
198+
// -> await for all to be available before drawing the image
199+
await Promise.all(loadedElements).then((elementArray) => {
200+
// create empty image
201+
ctx.clearRect(0, 0, width, height);
202+
// draw a random background color
203+
drawBackground();
204+
// store information about each layer to add it as meta information
205+
let attributesList = [];
206+
// draw each layer
207+
elementArray.forEach((element) => {
208+
drawElement(element);
209+
attributesList.push(getAttributeForElement(element));
210+
});
211+
// add an image signature as the edition count to the top left of the image
212+
signImage(`#${editionCount}`);
213+
// write the image to the output directory
214+
saveImage(editionCount);
215+
let nftMetadata = generateMetadata(newDna, editionCount, attributesList);
216+
metadataList.push(nftMetadata)
217+
console.log('- metadata: ' + JSON.stringify(nftMetadata));
218+
console.log('- edition ' + editionCount + ' created.');
219+
console.log();
220+
});
221+
dnaListByRarity[rarity].push(newDna);
222+
editionCount++;
169223
}
170224
writeMetaData(JSON.stringify(metadataList));
171225
};
172226

173-
startCreating();
227+
// Initiate code
228+
startCreating();

input/config.js

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const dir = __dirname;
55
const description = "This is an NFT made by the coolest generative code.";
66
const baseImageUri = "https://hashlips/nft";
77
const startEditionFrom = 1;
8-
const endEditionAt = 10;
98
const editionSize = 10;
109
const rarityWeights = [
1110
{
@@ -107,6 +106,5 @@ module.exports = {
107106
baseImageUri,
108107
editionSize,
109108
startEditionFrom,
110-
endEditionAt,
111109
rarityWeights,
112110
};

0 commit comments

Comments
 (0)