-
Notifications
You must be signed in to change notification settings - Fork 690
/
index.js
249 lines (223 loc) · 8 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
const fs = require("fs");
const { createCanvas, loadImage } = require("canvas");
const {
layers,
width,
height,
description,
baseImageUri,
editionSize,
startEditionFrom,
rarityWeights,
} = require("./input/config.js");
const console = require("console");
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
// saves the generated image to the output folder, using the edition count as the name
const saveImage = (_editionCount) => {
fs.writeFileSync(
`./output/${_editionCount}.png`,
canvas.toBuffer("image/png")
);
};
// adds a signature to the top left corner of the canvas
const signImage = (_sig) => {
ctx.fillStyle = "#000000";
ctx.font = "bold 30pt Courier";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText(_sig, 40, 40);
};
// generate a random color hue
const genColor = () => {
let hue = Math.floor(Math.random() * 360);
let pastel = `hsl(${hue}, 100%, 85%)`;
return pastel;
};
const drawBackground = () => {
ctx.fillStyle = genColor();
ctx.fillRect(0, 0, width, height);
};
// add metadata for individual nft edition
const generateMetadata = (_dna, _edition, _attributesList) => {
let dateTime = Date.now();
let tempMetadata = {
dna: _dna.join(""),
name: `#${_edition}`,
description: description,
image: `${baseImageUri}/${_edition}`,
edition: _edition,
date: dateTime,
attributes: _attributesList,
};
return tempMetadata;
};
// prepare attributes for the given element to be used as metadata
const getAttributeForElement = (_element) => {
let selectedElement = _element.layer.selectedElement;
let attribute = {
name: selectedElement.name,
rarity: selectedElement.rarity,
};
return attribute;
};
// loads an image from the layer path
// returns the image in a format usable by canvas
const loadLayerImg = async (_layer) => {
return new Promise(async (resolve) => {
const image = await loadImage(`${_layer.selectedElement.path}`);
resolve({ layer: _layer, loadedImage: image });
});
};
const drawElement = (_element) => {
ctx.drawImage(
_element.loadedImage,
_element.layer.position.x,
_element.layer.position.y,
_element.layer.size.width,
_element.layer.size.height
);
};
// check the configured layer to find information required for rendering the layer
// this maps the layer information to the generated dna and prepares it for
// drawing on a canvas
const constructLayerToDna = (_dna = [], _layers = [], _rarity) => {
let mappedDnaToLayers = _layers.map((layer, index) => {
let selectedElement = layer.elements.find(element => element.id === _dna[index]);
return {
location: layer.location,
position: layer.position,
size: layer.size,
selectedElement: {...selectedElement, rarity: _rarity },
};
});
return mappedDnaToLayers;
};
// check if the given dna is contained within the given dnaList
// return true if it is, indicating that this dna is already in use and should be recalculated
const isDnaUnique = (_DnaList = [], _dna = []) => {
let foundDna = _DnaList.find((i) => i.join("") === _dna.join(""));
return foundDna == undefined ? true : false;
};
const getRandomRarity = (_rarityOptions) => {
let randomPercent = Math.random() * 100;
let percentCount = 0;
for (let i = 0; i <= _rarityOptions.length; i++) {
percentCount += _rarityOptions[i].percent;
if (percentCount >= randomPercent) {
console.log(`use random rarity ${_rarityOptions[i].id}`)
return _rarityOptions[i].id;
}
}
return _rarityOptions[0].id;
}
// create a dna based on the available layers for the given rarity
// use a random part for each layer
const createDna = (_layers, _rarity) => {
let randNum = [];
let _rarityWeight = rarityWeights.find(rw => rw.value === _rarity);
_layers.forEach((layer) => {
let num = Math.floor(Math.random() * layer.elementIdsForRarity[_rarity].length);
if (_rarityWeight && _rarityWeight.layerPercent[layer.id]) {
// if there is a layerPercent defined, we want to identify which dna to actually use here (instead of only picking from the same rarity)
let _rarityForLayer = getRandomRarity(_rarityWeight.layerPercent[layer.id]);
num = Math.floor(Math.random() * layer.elementIdsForRarity[_rarityForLayer].length);
randNum.push(layer.elementIdsForRarity[_rarityForLayer][num]);
} else {
randNum.push(layer.elementIdsForRarity[_rarity][num]);
}
});
return randNum;
};
// holds which rarity should be used for which image in edition
let rarityForEdition;
// get the rarity for the image by edition number that should be generated
const getRarity = (_editionCount) => {
if (!rarityForEdition) {
// prepare array to iterate over
rarityForEdition = [];
rarityWeights.forEach((rarityWeight) => {
for (let i = rarityWeight.from; i <= rarityWeight.to; i++) {
rarityForEdition.push(rarityWeight.value);
}
});
}
return rarityForEdition[editionSize - _editionCount];
};
const writeMetaData = (_data) => {
fs.writeFileSync("./output/_metadata.json", _data);
};
// holds which dna has already been used during generation
let dnaListByRarity = {};
// holds metadata for all NFTs
let metadataList = [];
// Create generative art by using the canvas api
const startCreating = async () => {
console.log('##################');
console.log('# Generative Art');
console.log('# - Create your NFT collection');
console.log('##################');
console.log();
console.log('start creating NFTs.')
// clear meta data from previous run
writeMetaData("");
// prepare dnaList object
rarityWeights.forEach((rarityWeight) => {
dnaListByRarity[rarityWeight.value] = [];
});
// create NFTs from startEditionFrom to editionSize
let editionCount = startEditionFrom;
while (editionCount <= editionSize) {
console.log('-----------------')
console.log('creating NFT %d of %d', editionCount, editionSize);
// get rarity from to config to create NFT as
let rarity = getRarity(editionCount);
console.log('- rarity: ' + rarity);
// calculate the NFT dna by getting a random part for each layer/feature
// based on the ones available for the given rarity to use during generation
let newDna = createDna(layers, rarity);
while (!isDnaUnique(dnaListByRarity[rarity], newDna)) {
// recalculate dna as this has been used before.
console.log('found duplicate DNA ' + newDna.join('-') + ', recalculate...');
newDna = createDna(layers, rarity);
}
console.log('- dna: ' + newDna.join('-'));
// propagate information about required layer contained within config into a mapping object
// = prepare for drawing
let results = constructLayerToDna(newDna, layers, rarity);
let loadedElements = [];
// load all images to be used by canvas
results.forEach((layer) => {
loadedElements.push(loadLayerImg(layer));
});
// elements are loaded asynchronously
// -> await for all to be available before drawing the image
await Promise.all(loadedElements).then((elementArray) => {
// create empty image
ctx.clearRect(0, 0, width, height);
// draw a random background color
drawBackground();
// store information about each layer to add it as meta information
let attributesList = [];
// draw each layer
elementArray.forEach((element) => {
drawElement(element);
attributesList.push(getAttributeForElement(element));
});
// add an image signature as the edition count to the top left of the image
signImage(`#${editionCount}`);
// write the image to the output directory
saveImage(editionCount);
let nftMetadata = generateMetadata(newDna, editionCount, attributesList);
metadataList.push(nftMetadata)
console.log('- metadata: ' + JSON.stringify(nftMetadata));
console.log('- edition ' + editionCount + ' created.');
console.log();
});
dnaListByRarity[rarity].push(newDna);
editionCount++;
}
writeMetaData(JSON.stringify(metadataList));
};
// Initiate code
startCreating();