Ruby Pixel Art / Punk Art Programming Challenge #1 - 10 000 Dotty Spotty "Currency" Punks Inspired by Damin Hirst's "The Currency"

Hello,

  Anyone remember the Plany Ruby Quiz [1] - a fortnightly programming challenge?

  Yes, we are back with an artistic twist.

  Let's start a fortnightly pixel art / punk art programming
challenge. Yes, in ruby.

  How does it work?
  Let's pick a theme and then it's up to you - yes, you can - to put
together a new punk collection with your own artistic interpretation /
generation script. Yes, in ruby.

  Let's kick off with a theme inspired by
Damin Hirst's "The Currency" [2] token art parody
or is that cash grab with a paper with a bunch of random circles
starting at $20 000 a piece?

   Let's generate 10 000 free dotty spotty punks.
   For example - turn every pixel into a circle and
   maybe use different-sized variants of circles and maybe
  use a random offset "distortion" for the circle placement and so on.

  Yes, you can.

   Post your generative art script here. Questions and comments welcome.

  Happy pixel pushing in ruby. Cheers. Prost.

PS: New to pixel art / punk art programming in ruby? See Punk's Not Dead [3].

[1] GitHub - planetruby/quiz: Ruby Quiz is a fortnightly programming challenge for Ruby programmers. A new Ruby Quiz is sent to the Ruby Talk mailing list once every two weeks. Join us.
[2] https://www.heni.com
[3] CryptoPunks Not Dead - 24×24 Pixel Art on the Blockchain! · GitHub

Hello,

   to get you started with the Punk Art Challenge #1 I have put
together a little spots script [1]
that generates about a dozen punk variants with circles.

See the README page [2] for more right-click and save images.

  Yes, you can. Post your link or share your generative art script(s).
Yes, in ruby.

  Questions and comments welcome.

PS: The sample spots generative art machinery:

module Pixelart

class Image
  MAGICK_SCRIPT = "./tmp/magick_script.txt"
  MAGICK_INPUT  = "./tmp/magick_input.png"
  MAGICK_OUTPUT = "./tmp/magick_output.png"

def spots( spot=20,
              spacing: 0,
              center: nil,
              radius: nil,
              background: nil,
              lightness: nil )

  width  = @img.width
  height = @img.height

  canvas = Image.new( width*spot+(width-1)*spacing,
                      height*spot+(height-1)*spacing )
  canvas.save( MAGICK_INPUT )

  min_center, max_center = center ? center : [0,0]
  min_radius, max_radius = radius ? radius : [0,0]

  background_color = background ? Color.parse( background ) : 0

  min_lightness, max_lightness = lightness ? lightness : [0.0,0.0]

  script = String.new('')
  script << "#{MAGICK_INPUT} \\\n"

    width.times do |x|
      height.times do |y|
         color = base_img[ x, y ]

         if color == 0  ## transparent
           if background   ## change transparent to background
              color = background_color
           else
             next ## skip transparent
           end
         end

         if lightness
          h,s,l = Color.to_hsl( color, include_alpha: false )

           h = h % 360    ## make sure h(ue) is always positive!!!

           ## note: rand() return between 0.0 and 1.0
           l_diff = min_lightness +
                     (max_lightness-min_lightness)*rand()

           lnew = [1.0, l+l_diff].min
           lnew = [0.0, lnew].max

           color = Color.from_hsl( h,
                                   [1.0, s].min,
                                   lnew )
         end

         ## note: return hexstring with leading #
         # e.g.    0 becomes #00000000
         #        and so on
         color_hex = Color.to_hex( color, include_alpha: true )
         script << "-fill '#{color_hex}' "

         cx_offset,
         cy_offset = if center  ## randomize (offset off center +/-)
                       [(spot/2 + min_center) + rand( max_center-min_center ),
                        (spot/2 + min_center) + rand( max_center-min_center )]
                     else
                       [spot/2,   ## center
                        spot/2]
                     end

         cx = x*spot + x*spacing + cx_offset
         cy = y*spot + y*spacing + cx_offset

         px_offset = if radius ## randomize (radius +/-)
                       min_radius + rand( max_radius-min_radius )
                     else
                       spot/2
                     end
         px = cx + px_offset
         py = cy

         script << "-draw 'circle #{cx},#{cy},#{px},#{py}' \\\n"
      end
    end

    script << "-write #{MAGICK_OUTPUT}\n"

   File.open( MAGICK_SCRIPT, 'w:utf-8') { |f| f.write( script ) }

  MiniMagick::Tool::Magick.new do |magick|
    magick.script( MAGICK_SCRIPT )
  end

  Image.read( MAGICK_OUTPUT )
end
end # class Image
end # class Pixelart

[1] cryptopunks/spots.rb at master · cryptopunksnotdead/cryptopunks · GitHub
[2] cryptopunks/spots at master · cryptopunksnotdead/cryptopunks · GitHub

I skipped the punks and just played with circles from hashes.

Some example images seeded with the same, but random, hash:

$ ruby -r./circule.rb -e Circule.demo








1. Given an input hash
2. Slice the hash into 16-bit (possibly overlapping) slices
3. Subslice each slice to define a circle (x, y, r)
4. Color each pixel based on the number of circles it is inside

Code https://0x0.st/-gAt.rb

require 'uri'
require 'net/http'
require 'chunky_png'
require 'securerandom'

class Circule
  def initialize(
    hash: nil, btc_block: nil,
    bit0: 0, step: 16, bitn: 255,
    canvas: 24, ox: 0, oy: ox
  ) @_hash = hash.to_i(16) if hash
    @btc_block = btc_block
    @bit0 = bit0
    @step = step
    @bitn = bitn
    @canvas = canvas
    @ox = ox
    @oy = oy
  end

  def hash
    @_hash ||=
      if @btc_block
        Net::HTTP
        .get_response(
          URI("https://live.blockcypher.com/btc/block/#\{@btc\_block\}/&quot;\))
        .header['location']
        .split('/')[-1]
      else
        SecureRandom.hex(32)
      end
      .to_i(16)
  end

  def slices
    @_slices ||=
      @bit0
      .step(@bitn, @step)
      .map { |n| hash[n, 16] }
  end

  def circles
    @_circles ||=
      slices
      .map { |s| [ s[0, 5], s[5, 5], s[10, 6] ] }
  end

  def image
    @_image ||=
      ChunkyPNG::Image
      .new(@canvas, @canvas)
      .tap do |img|
        @canvas.times do |a|
          @canvas.times do |b|
            img[a, b] = ChunkyPNG::Color.html_color(
              ChunkyPNG::Color::PREDEFINED_COLORS.keys[
                circles
                .select { |x, y, r| (a+@ox-x)**2 + (b+@oy-y)**2 < r**2 }
                .size
              ]
            )
          end
        end
      end
  end

  class <<self
    def demo
      h = SecureRandom.hex(32)
      4.step(32) do |s|
        24.step(96, 24) do |c|
          Circule.new(hash: h, step: s, canvas: c).image.save(
            "/tmp/circ%02x%02x.png" % [s, c]
          )
        end
      end
    end
  end
end

eval DATA.read if __FILE__ == $0

__END__
require 'glimmer-dsl-libui'
include Glimmer
menu('Circule') { quit_menu_item { on_clicked { Kernel.exit } } }

@hash = SecureRandom.hex(32)
@bit0 = 0
@step = 16
@bitn = 255
@canvas = 24
@ox = 0
@oy = 0

@update = lambda do
  p [@bit0, @step, @bitn, @canvas, @ox, @oy]
  @image = Circule.new(
    hash: @hash,
    bit0: @bit0,
    step: @step,
    bitn: @bitn,
    canvas: @canvas,
    ox: @ox,
    oy: @oy,
  ).image.tap { |i| i.save('/tmp/circule.png') }
  sleep 0.0078125
end.tap { |p| p.call }

window { |w|
  vertical_box {
    form {
      horizontal_box {
        label('hash')
        entry.tap { |e|
          e.read_only = true
          e.text = @hash
        }
      }
      horizontal_box {
        label('bit0')
        slider(0, 255) {
          on_changed { |s| @bit0 = s.value; @update.call }
        }.value = @bit0
      }
      horizontal_box {
        label('step')
        slider(4, 128) {
          on_changed { |s| @step = s.value; @update.call }
        }.value = @step
      }
      horizontal_box {
        label('bitn')
        slider(0, 255) {
          on_changed { |s| @bitn = s.value; @update.call }
        }.value = @bitn
      }
      horizontal_box {
        label('canvas')
        slider(24, 120) {
          on_changed { |s| @canvas = s.value; @update.call }
        }.value = @canvas
      }
      horizontal_box {
        label('ox')
        slider(-120, 120) {
          on_changed { |s| @ox = s.value; @update.call }
        }.value = @ox
      }
      horizontal_box {
        label('oy')
        slider(-120, 120) {
          on_changed { |s| @oy = s.value; @update.call }
        }.value = @oy
      }
    }

    vertical_box {
    }
  }
}.show

···

On 9/23/21, Gerald Bauer <gerald.bauer@gmail.com> wrote:

Let's pick a theme and then it's up to you - yes, you can - to put
together a new punk collection with your own artistic interpretation /
generation script. Yes, in ruby.

Hello,

I [...] played with circles from hashes.
Some example images seeded with the same, but random, hash

  Wow. Thanks for your Ruby Pixel Art¹ Programming script.

  I put together a more "official" ruby pixel art / challenge page
with your script
as a first example [1].

To all - Join us. Yes, you can. Start from scratch or, yes, use any
library / gem you can find.

  Post your code snippets on the "official" Ruby Quiz Channel, that
is, the ruby-talk mailing list right here.

  Cheers. Prost.

[1] quiz/019 at master · planetruby/quiz · GitHub

···

---
¹: formerly Ruby Quiz

I [...] played with circles from hashes.

Wow. Thanks for your Ruby Pixel Art¹ Programming script.

I put together a more "official" ruby pixel art / challenge page
with your script as a first example [1].

A little screen recording adjusting settings with the glimmer UI:

https://0x0.st/-g1Y.mp4

···

On 10/2/21, Gerald Bauer <gerald.bauer@gmail.com> wrote:

[1] quiz/019 at master · planetruby/quiz · GitHub