Closed bladeninja888 closed 2 years ago
Ok figured it out, see code here if you use main:
var path = require("path"); 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"); var attributesList = []; var metadataList = []; var dnaList = [];
// 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")
);
};
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,
};
metadataList.push(tempMetadata);
return tempMetadata;
};
// prepare attributes for the given element to be used as metadata
const getAttributeForElement = (_element) => { let selectedElement = _element.layer.selectedElement; var parentDir = path.dirname(_element.layer.selectedElement.path); var baseDir = require("path").resolve(parentDir, ".."); var traitName = path.basename(baseDir); let attribute = { trait_type: traitName, value: 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, }; }); 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); };
const saveMetaDataSingleFile = (_editionCount) => {
fs.writeFileSync(
./output/${_editionCount}.json
,
JSON.stringify(metadataList.find((meta) => meta.edition == _editionCount))
);
};
// holds which dna has already been used during generation let dnaListByRarity = {}; // holds metadata for all NFTs // 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) => {
ctx.clearRect(0, 0, width, height);
let attributesList = [];
elementArray.forEach((element) => {
drawElement(element);
attributesList.push(getAttributeForElement(element));
});
saveImage(editionCount);
generateMetadata(newDna, editionCount, attributesList);
saveMetaDataSingleFile(editionCount);
console.log("- edition " + editionCount + " created.");
console.log();
});
dnaListByRarity[rarity].push(newDna);
dnaList.push(newDna);
editionCount++;
} writeMetaData(JSON.stringify(metadataList)); };
// Initiate code startCreating();
Need help as I try to do this step with code directly from V3 but does not work.
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)); }); // write the image to the output directory saveMetaDataSingleFile(editionCount); 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++; }
(node:5540) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined at Object.writeFileSync (fs.js:1517:5) at saveMetaDataSingleFile (/Users/chris/Desktop/xxx/index.js:162:6) at /Users/chris/Desktop/xxx/index.js:235:7 at processTicksAndRejections (internal/process/task_queues.js:95:5) at async startCreating (/Users/chris/Desktop/xxx/index.js:222:5) (Use
node --trace-warnings ...
to show where the warning was created) (node:5540) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag--unhandled-rejections=strict
(see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) (node:5540) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.