Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon-Becker committed Aug 31, 2021
1 parent 77efb1e commit dec5aa0
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
43 changes: 43 additions & 0 deletions README.md
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 added images/.keep
Empty file.
107 changes: 107 additions & 0 deletions index.py
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 added metadata/.keep
Empty file.
Binary file added trait-layers/.DS_Store
Binary file not shown.
Binary file added trait-layers/backgrounds/blue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/backgrounds/orange.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/backgrounds/purple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/backgrounds/red.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/backgrounds/yellow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/foreground/.DS_Store
Binary file not shown.
Binary file added trait-layers/foreground/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added trait-layers/text/.DS_Store
Binary file not shown.
Binary file added trait-layers/text/text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit dec5aa0

Please sign in to comment.