Inside the Punk Art Machinery - How To Generate 10 000 Punks (and Punkettes), Algorithmically - Paint by Numbers
The bad news - the formula for the original 10 000 algorithmically generated 24×24 CryptoPunks by LarvaLabs is a secret (and not included in the published open source code).
The good news - the "high-definition" 56×56 CryptoPunks remake on the Polkadot blockchain built with Substra(te) ("SubtraPunks") by Usetech includes the pixel art and code to generate algorithmically punks (and punkettes) with a fresh look.
Let's convert the punk_generator
art machinery.
Let's start with all the parts of a punk:
PARTS = {
face: { required: true,
attributes: [['', 'u'],
['', 'u']] },
mouth: { required: true,
attributes: [['Black Lipstick', 'f'],
['Red Lipstick', 'f'],
['Smile', 'u'],
['', 'u'],
['Teeth Smile', 'm'],
['Purple Lipstick', 'f']] },
nose: { required: true,
attributes: [['', 'u'],
['Nose Ring', 'u']] },
eyes: { required: true,
attributes: [['', 'u'],
['Asian Eyes', 'u'],
['Sun Glasses', 'u'],
['Red Glasses', 'u'],
['Round Eyes', 'u']] },
ears: { required: true,
attributes: [['', 'u'],
['Left Earring', 'u'],
['Right Earring', 'u'],
['Two Earrings', 'u']] },
beard: { required: false,
attributes: [['Brown Beard', 'm'],
['Mustache-Beard', 'm'],
['Mustache', 'm'],
['Regular Beard', 'm']] },
hair: { required: false,
attributes: [['Up Hair', 'm'],
['Down Hair', 'u'],
['Mahawk', 'u'],
['Red Mahawk', 'u'],
['Orange Hair', 'u'],
['Bubble Hair', 'm'],
['Emo Hair', 'u'],
['Thin Hair', 'm'],
['Bald', 'm'],
['Blonde Hair', 'f'],
['Caret Hair', 'f'],
['Pony Tails' 'f']] }
}
A punk is composed of seven parts, that is,
- Face
- Mouth
- Nose
- Eyes
- Ears
- Beard
- Hair
The first five parts are always required and the last two optional.
For every part there is a matching directory (e.g. /face
, /mouth
, /nose
, etc.) and
for every attribute there is a matching image with an index number starting at 1 (e.g. /face/face1.png
, face/face2.png
, etc.)
Find a cached copy of all images in the i/parts
directory -
looking something like:
/parts
├───face/
│ face1.png
│ face2.png
│
├───mouth/
│ mouth1.png
│ mouth2.png
│ mouth3.png
│ mouth4.png
│ mouth5.png
│ mouth6.png
│
├───nose/
│ nose1.png
│ nose2.png
│
├───eyes/
│ eyes1.png
│ eyes2.png
│ eyes3.png
│ eyes4.png
│ eyes5.png
│
├───ears/
│ ears1.png
│ ears2.png
│ ears3.png
│ ears4.png
│
├─── beard/
│ beard1.png
│ beard2.png
│ beard3.png
│ beard4.png
│
└───hair/
hair1.png
hair2.png
hair3.png
hair4.png
hair5.png
hair6.png
hair7.png
hair8.png
hair9.png
hair10.png
Or an all-together-now sneak preview:
Let's generate the famous Blond Punkette with:
- Red Lipstick (f)
- Nose Ring (u)
- Sun Glasses (u)
- Blonde Hair (f)
Note: The u/f/m stands for unisex/female/male and tells you what gender the attribute expects.
Let's code the artist known as generate_punk
- a method
that returns a (ready-to-save) punk image
from the attribute parts coded as numbers
starting at 1. If the code is 0 than
the (optional) part gets skipped.
codes = [2, 2, 2, 3, 1, 0, 10]
punk = generate_punk( codes )
punk.save( './punk-0000.png' )
And here's the magic paint by number art machinery:
require 'pixelart' ## helper library for pixel art images (in .png)
def generate_punk( codes )
punk = Pixelart::Image.new( 56, 56 )
PARTS.each_with_index do |(key,part),i|
code = codes[i]
if code != 0 ## if code 0 - skip optional part
## for debugging print attributes with names (size not 0, that is, "")
attribute = part[:attributes][ code-1 ]
puts "#{key}#{code} - #{attribute[0]} (#{attribute[1]})" if attribute[0].size > 0
## compose parts on top (from face to accessoire)
path = "./i/parts/#{key}/#{key}#{code}.png"
part = Pixelart::Image.read( path )
punk.compose!( part )
end
end
punk
end
Let's try for real:
codes = [2, 2, 2, 3, 1, 0, 10]
punk = generate_punk( codes )
punk.save( './punk-0000.png' )
printing:
mouth2 - Red Lipstick (f)
nose2 - Nose Ring (u)
eyes3 - Sun Glasses (u)
hair10 - Blonde Hair (f)
and let's add a 3x zoom factor:
punk3x = punk.zoom( 3 )
punk3x.save( './punk-0000x3.png' )
resulting in:
How does the machinery work? The algo generates an empty 56×56 pixel image / canvas and than adds - or is that composes - one part after the other on top all the way from face to hair. That's all the magic.
Let's generate another punk.
codes = [1, 5, 2, 3, 1, 1, 5]
punk = generate_punk( codes )
punk.save( './punk-0001.png' )
punk.zoom( 3 ).save( './punk-0001x3.png' )
printing:
mouth5 - Teeth Smile (m)
nose2 - Nose Ring (u)
eyes3 - Sun Glasses (u)
beard1 - Brown Beard (m)
hair5 - Orange Hair (u)
and resulting in:
Now let's generate 10 000, algorithmically! Yes, you can!