🐤
Welcome to the Generative GIF Engine v2.0.4 [8 minute read]
This python and node app generates layered-based gifs to create NFT gif art! It is faster, simpler, and produces higher quality gifs than any other open source gif generative tool out there. Export your animation as a png image sequence, organize your layer folders with rarity, and the code does the rest! I plan to actively maintain this repo and enhance it with various tools for months to come so be sure to ask questions in the discussion and write issues.
There are three steps:
- [Python] Converts layers into spritesheets using PIL. This step can be skipped if you already have the spritesheets, but is useful if you want to start with png files and makes the artist's life easier!
- [Node] Create generative spritesheets from the layers from step 1.
- The original idea came from MichaPipo's Generative Gif Engine but now most of the code in this step is forked from nftchef's Generative Engine which is forked from HashLips Generative Art Engine. Please check out Hashlip's
📺 Youtube /👄 Discord /🐦 Twitter /ℹ️ Website for a more in depth explanation on how the generative process works.
- The original idea came from MichaPipo's Generative Gif Engine but now most of the code in this step is forked from nftchef's Generative Engine which is forked from HashLips Generative Art Engine. Please check out Hashlip's
- [Python + gifski] Convert spritesheets to gifs using Python and gifski.
Checkout this Medium post and How does it work? for more information!
Here's an example final result (or you can download the code and run it and see more bouncing balls :)). It is also pushed to production on OpenSea.
EDIT tool now supports z-index/stacking, grouping and if-then statements. See nftchef's docs for more information. Here is an example of having one layer that is both in front and behind the ball.
Requirements
Install an IDE of your preference. Recomended
Install the latest version of Node.js
-
Run this command on your system terminal to check if node is installed:
node -v
Install the latest version of Python 3. I am currently using 3.8.1 but anything above 3.6 should work.
-
Run this command on your system terminal to check if node is installed:
python3 --version
Install gifski. I recommend using brew brew install gifski
if you're on Mac OSX. If you don't have brew you can install it using brew on Mac OSX. Or if you're on Windows you can install it using Chocolatey: choco install gifski
.
If none of those methods work, follow instructions on gifski gifski Github. Gifski is crucial for this tool because it provides the best gif generation out of all the tools I checked out (PIL, imageio, ImageMagic, js libraries).
If you plan on developing on this repository, run pre-commit
to install pre-commit hooks.
Installation
-
Download this repo and extract all the files.
-
Run this command on your root folder using the terminal:
make first_time_setup
If you have any issues with this command, try running each separate command:
python3 -m pip install --upgrade Pillow && pip3 install -r requirements.txt
cd step2_spritesheet_to_generative_sheet; npm i
brew install gifski
Each environment can be different, so try Google your issues. I'll add a few known issues below:
Known issues:
- M1 Mac: Canvas prebuild isn't built for ARM computers so you need to install it from their Github
cd
command might not work on Windows depending on what Terminal you are using. You may have to edit theMakefile
to useCHDIR
or the equivalent.- If you're on Windows 10 you might get a 'make' is not recognized. Try follow these instructions. Otherwise you can copy and paste the instructions manually in
Makefile
. - If you don't have brew installed, look at gifski docs for another way to install gifski.
How to run?
Load the png or gif files into the /layers
folder where each layer is a folder, and each folder contains another attribute folder which contains the individual frames and a rarity percentage. For example if you wanted a background layer you would have /layers/background/blue#20
and /layers/background/red#20
.
In each attribute folder, the frames should be named 0.png
-> X.png
or 0.gif
. See code or step 1 for folder structure. The code will handle any number of layers, so you could have a layer with two frames, another layer with one frame, and another with 20 frames, and as long as you pass numberOfFrames
= 20, then the layers will be repeated until they hit 20.
Update global_config.json
with:
'totalSupply'
: total number of gifs to generate.'height'
: height of one frame. This should be equal to width.'width'
: width of one frame. This should be equal to height.'framesPerSecond'
: number of frames per second. This will not be exact because PIL takes in integer milliseconds per frame (so 12fps = 83.3ms per frame but rounded to an int = 83ms). This will not be recognizable by the human eye, but worth calling out.'numberOfFrames'
: number of total frames. For example you could have 24 frames, but you want to render it 12fps.'description'
: description to be put in the metadata.'baseUri'
: baseUri to be put in the metadata.'saveIndividualFrames'
: this is if you want to save the individual final frames, for example if you want to let people pick just one frame for a profile page.'layersFolder'
: this is the folder that you want to use for the layers. The default islayers
, but this allows you to have multiple versions of layers and run them side by side. The current repo has four example folders,layers
,layers_grouping
,layers_if_then
,layers_z_index
which all demonstrate features from nftchef's repo.'quality'
: quality of the gif, 1-100.
Update step2_spritesheet_to_generative_sheet/src/config.js
with your layerConfigurations
. If you want the basic configuration, just edit layersOrder
, but if you want to take advantage of nftchef's repo, then scroll through the file for some examples and modify layerConfigurations
accordingly.
-
To run the process end to end run:
make all
Your output gifs and JSON metadata will appear in build/gifs
and build/json
. Try it yourself with the default settings and layers!
How does it work?
Step 1
In order to get nftchef's Generative Gif Engine to work, the input layers needs to be in Sprite Sheet. However this is tedious and unintuitive for many artists who use tools that export individual images.
Step 1 simply converts individual images to spritesheets with the rarity percentage. You provide the various layers in the /layers
folder with the rarity in the folder name. Each image should be numbered from 0 -> X, and only accepts .png
.
If you do not include the rarity weight in the attribute folder name, that attribute will be ignored
You can provide any number of frames in each layer folder, the code will repeat them up until it hits numberOfFrames
. It will also trim any that have too many frames.
Example layers folder structure with four layers and two traits each layer:
layers
└───Background
│ └───Grey#50
│ │ 0.png
│ └───Pink#50
│ │ 0.png
└───Ball
│ └───Blue#50
│ │ 0.png
│ │ 1.png
│ │ 2.png
│ │ ...
│ └───Green#50
│ │ 0.png
│ │ 1.png
│ │ 2.png
│ │ ...
└───Hat
│ └───Birthday#50
│ │ 0.png
│ │ 1.png
│ │ 2.png
│ │ ...
│ └───Cowboy#50
│ │ 0.png
│ │ 1.png
│ │ 2.png
│ │ ...
└───Landscape
│ └───Cupcake#50
│ │ 0.png
│ └───Green Tower#50
│ │ 0.png
Example layer:
Background:
Grey:
Pink:
Ball:
Blue:
Green:
Hat:
Birthday:
Cowboy:
Landscape:
Cupcake:
Green Tower:
I am using python here instead of javascript libraries because I have found that image processing using PIL is much faster and without lossy quality than javascript. These benefits are much clearer in step 3.
You can run only step1 by running:
make step1
This will convert the pngs into spritesheets and the output will look something like this:
Output:
Background:
Grey#50.png:
Pink#50.png:
Ball:
Blue#50.png:
Green#50.png:
Hat:
Birthday#50.png:
Cowboy#50.png:
Landscape:
Cupcake#50.png:
Green Tower#50.png:
EDIT tool now supports z-index/stacking, grouping and if-then statements. See nftchef's docs for more information. The layers in this step will have to match the format expected in step 2. See the example layer folders for some more info.
EDIT tool now supports gif layers. You can provide layers as gifs and the code will split the gif into frames. See layers_gif_example
. It will create a temp folder in step1_layers_to_spritesheet/temp with the resulting separate frames, and then will parse through that folder to create the output. Make sure numberOfFrames
is set in global_config.json.
Step 2
Step 2 takes the spritesheets from step 1 and generates all possible combinations based on rarity. This is where all the magic happens! The output is a bunch of spritesheets with all the layers layered on top of each other.
The original idea came from MichaPipo's Generative Gif Engine but now most of the code in this step is forked from nftchef's Generative Engine which is forked from HashLips Generative Art Engine. Please check out Hashlip's
I recently modified this section to use the code from nftchef's Generative Engine which adds the following features:
- if-then statements. You can have generative art code that says if this layer, then select these layers. There is an example layers under
layers_if_then
which has logic for if the ball is pink, wear a birthday or cowboy hat, or if the ball is purple, wear a mini ball hat. See nftchef's docs for more information. - grouping statements. You can now group traits into certain groups. So in the
layers_grouping
we have common balls and hats, and rare balls and hats, and the firsttotalSupply - 1
balls are common, and the last one is rare. - z-index otherwise known as stack order. You can now have multiple stacks for the same layer, for example a basketball hoop landscape which has art in front and behind the ball. See nftchef's docs for more information.
You will need to update global_config.json
and also update layerConfigurations
in step2_spritesheet_to_generative_sheet/src/config.js
.
You can run only step 2 by running:
make step2
Example output with the layers
folder (only first 4 displayed, but there are 16 total):
Example output with the layers_z_index
folder:
Step 3
Step 3 takes the spritesheets from step 2 and creates gifs in builds/gifs
. This is where Python libraries really shine. Initially I used PIL, but found some issues with pixel quality.
In MichaPipo's original repo, they used javascript libraries to create the gifs. These copied pixel by pixel, and the logic was a bit complicated. Creating just 15 gifs would take 4 minutes, and I noticed some of the pixel hex colors were off. Also depending on CPU usage, the program would crash. I spent days debugging, when I just decided to start from scratch in another language.
Initially I tried PIL, imageio, and a few Python libraries, but they all had issues generating gifs. I spent weeks finding the best tool for this job, and came across gifski. This creates incredibly clean gifs and worked the best.
Now, generating 15 gifs takes < 30 seconds and renders with perfect pixel quality!
You can change the framesPerSecond
in global_config.json
and you can run only step 3 by running:
make step3
This allows you to not have to regenerate everything to play around with fps.
Example output with all 16 permutations (click on each gif for the 1000x1000 version):
If you set saveIndividualFrames
to true
in global_config.json
, it will also split the gifs into individual frames and save them in images
. This is useful if you want people to be able to choose a single frame for a profile picture.
Some metrics:
MichaPipo's Generative Gif Engine:
- 15 NFT - 5 minutes with sometimes incorrect pixels.
- 100 NFT - one hour (with the computer being almost unusable).
New Generative Gif Engine:
- 15 NFT - 30 seconds with no pixel issues.
- 100 NFT - 3 minutes and 17 seconds with no pixel issues.
- 1000 NFT - 45 minutes with no pixel issues and no CPU issues.
Rarity stats
You can check the rarity stats of your collection with:
make rarity
Provenance Hash Generation - IN PROGRESS
THIS SECTION IS STILL IN PROGRESS, IT DOES NOT GENERATE PROVENANCE HASH CORRECTLY
If you need to generate a provenance hash (and, yes, you should, read about it here ), make sure the following in config.js is set to true
// IF you need a provenance hash, turn this on
const hashImages = true;
Then… After generating images and data, each metadata file will include an imageHash
property, which is a Keccak256 hash of the output image.
To generate the Provenance Hash
run the following util
make provenance
The Provenance information is saved to the build directory in _prevenance.json
. This file contains the final hash as well as the (long) concatenated hash string.
*Note, if you regenerate the images, You will also need to regenerate this hash.
Update your metadata info
You can change the description and base Uri of your metadata even after running the code by updating global_config.json
and running:
make update_json
Solana metadata
After running make all
you can run generate the Solana metadata in two steps:
- Edit
step2_spritesheet_to_generative_sheet/Solana/solana_config.js
make solana
to generate the Solana metadata. This will create an output folderbuild/solana
with the gifs and the metadata.
Most of the code comes from nftchef.
I have not tried this on any test net or production Solana chain, so please flag any issues or create a PR to fix them!
IMPORTANT NOTES
All of the code in step1 and step3 was written by me. The original idea for the repo came from MichaPipo's Generative Gif Engine but now most of the code in step 2 is forked from nftchef's Generative Engine which is forked from HashLips Generative Art Engine.
_ Things to work on: _
- Update step2 with latest features from Hashlips art engine.
- Add layer functionality to step2 from nftchef art engine.
- Allow passing in gifs into step1 to split into spritesheets.
FAQ
Q: Why did you decide to use Python for step 1 and step 3?
A: I found that Python PIL work better and faster than JS libraries, and the code is simpler for me. Initially I tried PIL, imageio, and a few Python libraries, but they all had issues generating gifs. I spent weeks finding the best tool for this job, and came across gifski. This creates incredibly clean gifs and worked the best.
My philosophy is pick the right tool for the right job. If someone finds a better library for this specific job, then let me know!
Q: Why didn't you use Python for step 2?
A: The NFT dev community which writes the complicated logic for generative art mainly codes in javascript. I want to make it easy to update my code and incorporate the best features of other repos as easily as possible, and porting everything to Python would be a pain. You can imagine step 1 and step 3 are just helper tools in Python, and step 2 is where most of the business logic comes from.
Be sure to follow me for more updates on this project: