Tubby Cats is an art-based NFT project on Ethereum, created by @tubbystudios. As the developer responsible for image generation, I wanted to ensure the final collection would showcase each artists’ work and that every Tubby Cat would feel unique.
NFTs have become an increasingly popular way to showcase art and identity. Last year the industry saw an explosion of projects that were utilized as profile pictures on social media. This inspired the team to create a project where everyone would be able to find a Tubby Cat that they feel represents themselves.
This is why we put so much time & care into the Tubby Cats generation, generating over 150,000 images to review and refine before we launched our final collection of 20,000 Tubby Cats — so everyone could find their perfect pal.
After our first handful of test gens, something seemed off. The individual traits were incredible and distinct, but when all of the assets were genned together, the composed images didn’t feel right.
At this time, I was generating Tubby Cats with a generic image generation algorithm, which is what a majority of NFT projects use. These algorithms typically use pre-determined rarities for each trait to compose images. Images generated from these algorithms can often look mismatched because of the complete randomness.
However, we wanted each Tubby Cat to feel unique. We wanted to make sure there was a Tubby Cat for everyone.
To achieve this, I created an image generation algorithm based on gen palettes.
A gen palette
is a term I came up with to categorize traits and trait types based on colors and themes. Each individual trait was entered into one or multiple gen palettes. Traits would only be genned with other traits that were part of the same palette.
Below are Tubby Cats generated from each type of algorithm:
There’s a substantial difference in the above images. The images generated from gen palettes are aesthetic, cohesive, and most importantly: they appear to be created with intention.
The Tubby Cats gen palettes were either based on colors (e.g., Pink & Blue, Pastels, Monochrome) or theme (e.g., Fancy, Menhera, E-Girl). To create a gen palette, you would need to sort traits of each trait type into separate folders and then specify which folders should be genned for that palette.
Note: I do not think gen palettes are feasible for every NFT collection to utilize. Gen palettes worked well for Tubby Cats because of our unusually large number of traits (1600+) and our various trait types (17).
{
"backgrounds": ["E-Girl"],
"bodies": ["Ube", "Cotton Candy", "Mochi"],
"tops": ["E-Girl"],
"mouths": ["E-Girl"],
"hats": ["E-Girl"],
"clips": ["E-Girl"],
"misc": ["E-Girl"],
"hats_with_hair": ["E-Girl"],
"sunglasses": ["E-Girl"],
"eyewear": ["E-Girl"],
"hair_colors": ["E-Girl"],
"ear_piercings": ["Silver"],
"hand_colors": ["E-Girl"],
"eye_colors": ["Pink", "Purple", "Leak Eyes Pink", "Leak Eyes Purple"],
"pets": [""],
"costumes": [""],
"whole_head_hats": [""]
}
The above snippet is the JSON object used to generate our E-Girl palette. It specifies which folders it should pull from for all of the traits (Backgrounds, Bodies, Tops, etc.) and which trait types it should avoid (Pets, Costumes, and Whole Head Hats).
If you’re interested, you can view the final images generated from this palette on Opensea here.
The most time-consuming part of generating with palettes is asset sorting and organization. For each trait type, we created folders for each palette that used that trait type. Below demonstrates our palette-based organization for Tops.
If we open any of these folders, we can see the individual traits:
Including single trait rarity while using gen palettes is difficult and can easily over-complicate the image generation process. To avoid over-complication, we set rarities based on palettes instead of by single traits. This is how we broke it down:
9 single color palettes: 15%
42 double color palettes: 50%
19 triple color palettes: 20%
41 themed palettes: 15%
This solved the issue of determining single trait rarity (very hard to do with 1600+ unique traits) and because the traits we wanted to remain rare were primarily in hyper-specific themed palettes, they were generated less-often.
Below is a list all the palettes we created and used in the final collection:
The algorithm starts by picking a random number between 1 and 100 to decide which type of palette will be used - single, double, triple, or themed. After the palette type is determined, the code will pick a random palette from that type.
# Random number to determine which palette type will be genned
random_palette_type_int = random.randint(0, 100)
if (random_palette_type_int <= 15):
# Use a single color palette
rand_single_int = random.randint(0, len(single_palettes) - 1)
basepaths = single_palettes[rand_single_int]
elif (random_palette_type_int <= 65):
# Use a double color palette
rand_double_int = random.randint(0, len(double_palettes) - 1)
basepaths = double_palettes[rand_double_int]
elif (random_palette_type_int <= 85):
# Use a triple color palette
rand_triple_int = random.randint(0, len(triple_palettes) - 1)
basepaths = triple_palettes[rand_triple_int]
else:
# Use a themed "special" color palette
rand_special_int = random.randint(0, len(special_palettes) - 1)
basepaths = special_palettes[rand_special_int]
Once the specific palette has been determined, the code will grab all of the images & image names of every folder for each trait type.
# Get all files & filenames for Tops from paths specified in the theme's JSON object
def get_tops(path_names):
top_images = []
top_names = []
for path_name in path_names:
path = (f"./images/Tops Sorted/{path_name}")
for f in os.listdir(path):
valid_images = [".png", ".PNG", ".Png"]
ext = os.path.splitext(f)[1]
img_name = os.path.splitext(f)[0]
if ext.lower() not in valid_images:
continue
top_names.append(img_ame)
top_images.append(Image.open(os.path.join(path, f)))
return [top_images, top_names]
In the main
function of the image generation, we repeat this for all specified trait types of each palette. (Palettes are not required to use every trait type. For example, the E-Girl palette does not have Pets).
Once all of the individual traits have been selected for an image, we run a series of checks for traits that clip with one another. Depending on which traits the code selects, the image may need to be composed/layered differently. Once the image is composed, it’s saved along with the corresponding metadata.
From our test gens, we noticed some of our face traits didn’t fit well with each other (Mouth, Eyes, Eyebrows). We separated these assets into different categories so the faces made sense.
Eyebrows consisted of 3 categories: Regular, Sad, and Scared.
Eyes were sorted into 7 categories: Regular, Sad, Scared, Sleepy, Special, Colored, and Other - which included unique eye types like Dizzy, Leak, and MultiEyes.
Mouths were sorted into 5 categories: Regular, Sad, Scared, Special, and Accessory - which included theme-based mouths.
Below are a few scenarios that could be produced from the algorithm. Trait types are chosen randomly. As you’ll see below certain traits are not required.
Randomness ensures each Tubby Cat is unique - not only by palette, but by amount and types of traits as well.
Below are examples of images generated from various themes in the final collection:
How do you efficiently review 20,000 images and their corresponding metadata?
We created an internal review tool that allowed the team to review the Tubby Cats one-by-one to check for any issues such as clips (conflicting traits) and incorrect metadata.
It took the team about 2 weeks to review all of the images and replace where necessary. Tubby Cats that needed replacement were replaced with hand-made Tubby Cats and one-of-ones.
To ensure certain traits were included together and the highest quality Tubby Cats were included in the collection, we created another internal website where our team could “dress-up” Tubby Cats.
These hand-made Tubby Cats would be sent to a Discord channel with their metadata which would be used to up-res the images. In total, roughly 2000 hand-made Tubby Cats are in the final collection.
An NFT to compliment this article has been minted on ZORA and is available to mint for 0.042 ETH with 420 editions available. Minting is available here.
Art is by @sugoiNFT.
I hope Tubby Cats inspires NFT creators to utilize gen palettes. Generating images based on palettes makes a huge difference and allows artists’ work to be better appreciated.
There’s a Tubby Cat for everyone. What’s yours?
If you have questions about implementing gen palettes or just want to chat, you can find me on Twitter.