-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
77efb1e
commit dec5aa0
Showing
15 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
|
||
# sudoku-solver-py | ||
nft-generator-py is a python based NFT generator which programatically generates unique images using weighted layer files. The program is simple to use, and new layers can be added by adding a new layer object and adding names, weights, and image files to the object. | ||
|
||
## How it works | ||
- A call to `generate_unique_images(amount, config)` is made, which is the meat of the application where all the processing happens. | ||
- The `config` object is read and for each object in the `layers` list, random values are selected and checked for uniqueness against all previously generated metadata files. | ||
- Once we have `amount` unique tokens created, we layer them against eachother and output them and their metadata to their respective folders, `./metadata` and `./images`. | ||
|
||
### Configuration | ||
``` | ||
{ | ||
"layers": [ | ||
{ | ||
"name": "Background", | ||
"values": ["Blue", "Orange", "Purple", "Red", "Yellow"], | ||
"trait_path": "./trait-layers/backgrounds", | ||
"filename": ["blue", "orange", "purple", "red", "yellow"], | ||
"weights": [30, 45, 15, 5, 10] | ||
}, | ||
... | ||
], | ||
"name": "NFT #" | ||
} | ||
``` | ||
|
||
The `config` object is a dict that contains `layers` and `name` objects that can be changed to produce different outputs when running the program. Within metadata files, tokens are named using the configuration's `name` parameter. | ||
- In ascending order, tokenIds are appended to the `name` resulting in NFT metadata names such as NFT #0001. | ||
- tokenIds are padded to the largest amount generated. IE, generating 999 objects will result in names NFT #001, using the above configuration, and generating 1000 objects will result in NFT #0001. | ||
|
||
The `layers` list contains `layer` objects that define the layers for the program to use when generating unique tokens. Each `layer` has a name, which will be displayed as an attribute, values, trait_path, filename, and weights. | ||
- `trait_path` refers to the path where the image files in `filename` can be found. Please note that filenames omit .png, and it will automatically be prepended. | ||
|
||
- `weight` corresponds with the percent chance that the specific value that weight corresponds to will be selected when the program is run. The weights must add up to 100, or the program will fail. | ||
|
||
#### Troubleshooting | ||
- All images should be in .png format. | ||
- All images should be the same size in pixels, IE: 1000x1000. | ||
- The weight values for each attribute should add up to equal 100. | ||
|
||
### Credits | ||
This project is completely coded by [Jonathan Becker](https://jbecker.dev), using no external libraries. | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from IPython.display import display | ||
from PIL import Image | ||
import random | ||
import json | ||
|
||
def create_new_image(all_images, config): | ||
new_image = {} | ||
for layer in config["layers"]: | ||
new_image[layer["name"]] = random.choices(layer["values"], layer["weights"])[0] | ||
|
||
if new_image in all_images: | ||
return create_new_image(all_images, config) | ||
else: | ||
return new_image | ||
|
||
def generate_unique_images(amount, config): | ||
pad_amount = len(str(amount)); | ||
trait_files = { | ||
|
||
} | ||
for trait in config["layers"]: | ||
trait_files[trait["name"]] = {} | ||
for x, key in enumerate(trait["values"]): | ||
trait_files[trait["name"]][key] = trait["filename"][x]; | ||
|
||
all_images = [] | ||
for i in range(amount): | ||
new_trait_image = create_new_image(all_images, config) | ||
all_images.append(new_trait_image) | ||
|
||
i = 0 | ||
for item in all_images: | ||
item["tokenId"] = i | ||
i += 1 | ||
|
||
for i, token in enumerate(all_images): | ||
attributes = [] | ||
for key in token: | ||
if key != "tokenId": | ||
attributes.append({"trait_type": key, "value": token[key]}) | ||
token_metadata = { | ||
"image": "./images/" + str(i).zfill(pad_amount) + '.png', | ||
"tokenId": i, | ||
"name": config["name"] + str(i).zfill(pad_amount), | ||
"attributes": attributes | ||
} | ||
with open('./metadata/' + str(i).zfill(pad_amount) + '.json', 'w') as outfile: | ||
json.dump(token_metadata, outfile, indent=4) | ||
|
||
with open('./metadata/all-objects.json', 'w') as outfile: | ||
json.dump(all_images, outfile, indent=4) | ||
|
||
for item in all_images: | ||
layers = []; | ||
for index, attr in enumerate(item): | ||
if attr != 'tokenId': | ||
layers.append([]) | ||
layers[index] = Image.open(f'{config["layers"][index]["trait_path"]}/{trait_files[attr][item[attr]]}.png').convert('RGBA') | ||
|
||
if len(layers) == 1: | ||
rgb_im = layers[0].convert('RGB') | ||
file_name = str(item["tokenId"]).zfill(pad_amount) + ".png" | ||
rgb_im.save("./images/" + file_name) | ||
elif len(layers) == 2: | ||
main_composite = Image.alpha_composite(layers[0], layers[1]) | ||
rgb_im = main_composite.convert('RGB') | ||
file_name = str(item["tokenId"]) + ".png" | ||
rgb_im.save("./images/" + file_name) | ||
elif len(layers) >= 3: | ||
main_composite = Image.alpha_composite(layers[0], layers[1]) | ||
layers.pop(0) | ||
layers.pop(0) | ||
for index, remaining in enumerate(layers): | ||
main_composite = Image.alpha_composite(main_composite, remaining) | ||
rgb_im = main_composite.convert('RGB') | ||
file_name = str(item["tokenId"]) + ".png" | ||
rgb_im.save("./images/" + file_name) | ||
|
||
generate_unique_images(5, { | ||
"layers": [ | ||
{ | ||
"name": "Background", | ||
"values": ["Blue", "Orange", "Purple", "Red", "Yellow"], | ||
"trait_path": "./trait-layers/backgrounds", | ||
"filename": ["blue", "orange", "purple", "red", "yellow"], | ||
"weights": [30, 45, 15, 5, 10] | ||
}, | ||
{ | ||
"name": "Foreground", | ||
"values": ["Python Logo"], | ||
"trait_path": "./trait-layers/foreground", | ||
"filename": ["logo"], | ||
"weights": [100] | ||
}, | ||
{ | ||
"name": "Branding", | ||
"values": ["A Name"], | ||
"trait_path": "./trait-layers/text", | ||
"filename": ["text"], | ||
"weights": [100] | ||
} | ||
], | ||
"name": "NFT #" | ||
}) | ||
|
||
#Additional layer objects can be added following the above formats. They will automatically be composed along with the rest of the layers as long as they are the same size as eachother. | ||
#Objects are layered starting from 0 and increasing, meaning the front layer will be the last object. (Branding) |
Empty file.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.