Home

Awesome


Breaking News - Looking for the 100 Ordinal Punks collection inscribed into the bitcoin blockchain on February 9th, 2023? See 100 Ordinal Punks - The Free White Label Quick Starter Edition »


Q: Dear sir, how do I get rich in bits-coin punks?

A: If we all buy bits-coin punks from one another at ever higher prices we'll all be rich beyond our wildest dreams.

21 million bits-coin. 10 000 punks. Do the math.

Punks - The Free White Label Quick Starter Edition

Let's (re)create from zero / scratch a pixel-perfect copy of the Matt & John's® 10 000 punks collection (Anno 2017).

Yes, you can. Do-it-yourself (DIY) and own 100% forever your home-made free clean-room copy of the billion dollar (2400×2400) bitmap that kicked-off a trillion dollar get-rich-quick digital art mania / bubble in 2021 - selling "decentralized" blockchain tokens database records to ever greater fools at ever higher prices.

Inside the Magic Money Machine - Mint 10 000 Unique Punks from 11 Archtetypes 'n' 122 Attributes

Let's copy and (re)use all punk (building) blocks in the basic series (24×24):

11 Archetypes:

Male 1/2/3/4 , Female 1/2/3/4 , Zombie , Ape , Alien

<!-- note: sort attributes a-z for now - why? why not? -->

122 Attributes (by category and a-z):

<!-- break -->

(Source: Punk (Building) Blocks - Basic Series (24×24))

Let's wipe up a generate_punk method that pastes / composes together the building blocks / attributes and returns a ready-to-save image. Example:

# generate punk #0
punk = generate_punk( 'Female 2', 'Earring', 'Blonde Bob', 'Green Eye Shadow' )
punk.save( "./tmp/punk0.png" )
punk.zoom(20).save( "./tmp/punk0@20x.png" )

# generate punk #1
punk = generate_punk( 'Male 1', 'Smile', 'Mohawk' )
punk.save( "./tmp/punk1.png" )
punk.zoom(20).save( "./tmp/punk1@20x.png" )

Here we go - the billion dollar formula:

require 'pixelart'


def normalize( str )
  ## allow (ignore):
  ##    space ( ),
  ##    underscore (_),
  ##    dash (-)
  str.downcase.gsub( /[ _-]/, '' ).strip
end


def generate_punk( *values, dir: "./basic" )
  punk_type       = values[0]
  attribute_names = values[1..-1]

  punk_type = normalize( punk_type )
  path      = "#{dir}/#{punk_type}.png"
  punk = Image.read( path )

  m_or_f = if punk_type.index( 'female' )
             'f'
           else
             'm'
           end

  attribute_names.each do |attribute_name|
     next if attribute_name.nil? || attribute_name.empty?  ## note: skip nil/empty attributes

     attribute_name = normalize( attribute_name )
     path           = "#{dir}/#{m_or_f}/#{attribute_name}.png"
     attribute      = Image.read( path )

     punk.compose!( attribute )
  end

  punk
end # method generate

Let's test drive punk #0 and punk 1 (see above) and voila! In the original 24×24 format:

And 20x (480×480):

Note: If you use your own building blocks make sure your type and attribute names match the filenames (without the .png extension). For the matching algorithm all names get automatically downcased and all spaces deleted, thus, Male 1 will map to male1.png and 3D Glasses to 3dglasses.png and Knitted Cap to knittedcap.png and so on.

Let's read-in all meta data records for all 10 000 punks. See the punks.csv dataset that reads:

type, attribute1, attribute2, attribute3, attribute4, attribute5, attribute6, attribute7
Female 2, Earring, Blonde Bob, Green Eye Shadow,,,,
Male 1, Smile, Mohawk,,,,,
Female 3, Wild Hair,,,,,,
Male 1, Wild Hair, Pipe, Nerd Glasses,,,,
Male 2, Goat, Earring, Wild Hair, Big Shades,,,
Female 2, Earring, Half Shaved, Purple Eye Shadow,,,,
Male 2, Do-rag,,,,,,
Female 2, Spots, Wild White Hair, Clown Eyes Blue,,,,
Male 1, Luxurious Beard, Messy Hair,,,,,
Male 2, Big Beard, Police Cap, Clown Nose,,,,
Female 1, Mohawk, Blue Eye Shadow,,,,,
Female 2, Black Lipstick, Straight Hair Dark, Clown Eyes Green,,,,
...

Let's try:

require 'csvreader'

recs = CsvHash.read( './punks.csv' )
puts "#{recs.size} punk(s)"
#=> 10 000 punk(s)

And to wrap up let's loop over all 10 000 punks and generate a 24×24 version and a 20x zoom, that is, 480×480. Let's go:

recs.each_with_index do |rec,i|
  puts "==> punk #{i}:"
  pp rec

  values = rec.values
  punk = generate_punk( *values )

  name = "punk#{i}"

  punk.save( "./o/#{name}.png" )
  punk.zoom(20).save( "./o/#{name}@20x.png" )
end

Yes, that's it. In the /o directory you will now find two images per punk - in the orginal format, that is, 24×24 and 20x, that is, 480×480 - and get:

o/
  punk0.png
  punk0@20x.png
  punk1.png
  punk1@20x.png
  punk2.png
  punk2@20x.png
  punk3.png
  punk3@20x.png
  punk4.png
  punk4@20x.png
  punk5.png
  punk5@20x.png
  punk6.png
  punk6@20x.png
  punk7.png
  punk7@20x.png
  punk8.png
  punk8@20x.png
  ...

Let's open up the first hundred punks, that is, punk0.png to punk99.png:

And let's have a looksie at the biggie 20x (480×480) versions:

and so on.

Bonus - Generate an All-In-One (2400×2400) Composite Image

Let's generate an all-in-one composite image holding the complete collection of 10 000 punks in a 100×100 grid.

punks = CompositeImage.new( 100, 100 )

recs.each_with_index do |rec,i|
  values = rec.values
  punk = generate_punk( *values )

  punks << punk
end

punks.save( "./o/punks.png" )

Yes, that's it. Open up punks.png to have a looksie.

Note: Only showing the first one hundred punks as a preview. Download punks.png ~800k for the full monty.

Questions? Comments?

Post them on the D.I.Y. Punk (Pixel) Art reddit. Thanks.