Ruby (Vector) 2D Graphics Programming ("Less Is More" or "Code Golfing") Challenge - Draw 24x24 Pixel Punks w/ (Vector) 2D Graphics Instructions (LineTo, MoveTo, ...)

Hello,

  here's the idea / the code golfing challenge:

Let's draw 24x24 pixel punks with 2d graphics instruction e.g lineto,
moveto, etc. -
thus, vectorizing pixel punks.

The "official" version in the scalable vector graphics (SVG) format is
using a "generic" brute-force "algorithm" that turns every pixel into
a rectangle.

  Can you do better? Let's "code golf" - that is - less (2D graphics
draw instructions - yes, in ruby) is more.

  Let's try (and why not start with ) the $7+ million alien punk #3100
[1] with a headband? Anyone?

  Questions and comments welcome.

[1] CryptoPunks: Details for Punk #3100

That looks interesting. We are using R for data plotting, can this be an alternative to R?

Thanks

···

在 2021年11月6日,下午9:12,Gerald Bauer <gerald.bauer@gmail.com> 写道:

Hello,

here's the idea / the code golfing challenge:

Let's draw 24x24 pixel punks with 2d graphics instruction e.g lineto,
moveto, etc. -
thus, vectorizing pixel punks.

The "official" version in the scalable vector graphics (SVG) format is
using a "generic" brute-force "algorithm" that turns every pixel into
a rectangle.

Can you do better? Let's "code golf" - that is - less (2D graphics
draw instructions - yes, in ruby) is more.

Let's try (and why not start with ) the $7+ million alien punk #3100
[1] with a headband? Anyone?

Questions and comments welcome.

[1] CryptoPunks: Details for Punk #3100

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<ruby-talk list: member options login page>

Hi,

The Canvas Shape DSL
(glimmer-dsl-swt/GLIMMER_GUI_DSL_SYNTAX.md at master · AndyObtiva/glimmer-dsl-swt · GitHub)
and Canvas Path DSL
(glimmer-dsl-swt/GLIMMER_GUI_DSL_SYNTAX.md at master · AndyObtiva/glimmer-dsl-swt · GitHub)
provide Ruby declarative syntax for building an image via vector
graphics:

Here is an example where they are utilized to build an image from
various shapes:

Another example that relies on cubic curves:

include Glimmer

shell {
  text 'Canvas Path Example'
  minimum_size 300, 300

  canvas {
    path {
      foreground :black
      250.times {|n|
        cubic(n + n%30, n+ n%50, 40, 40, 70, 70, n + 20 + n%30, n%30*-1 * n%50)
      }
    }
  }

}.open

In fact, you could build composite shape abstractions out of simpler
ones (like a `bevel` from `polygon`s) and then utilize that directly.
Here is a script that does that and saves the output image into a
file:

Feel free to utilize for the challenge!

···

On Sat, Nov 6, 2021 at 9:24 AM <pyh@stackdev.eu> wrote:

That looks interesting. We are using R for data plotting, can this be an alternative to R?

Thanks

> 在 2021年11月6日,下午9:12,Gerald Bauer <gerald.bauer@gmail.com> 写道:
>
> Hello,
>
> here's the idea / the code golfing challenge:
>
> Let's draw 24x24 pixel punks with 2d graphics instruction e.g lineto,
> moveto, etc. -
> thus, vectorizing pixel punks.
>
> The "official" version in the scalable vector graphics (SVG) format is
> using a "generic" brute-force "algorithm" that turns every pixel into
> a rectangle.
>
> Can you do better? Let's "code golf" - that is - less (2D graphics
> draw instructions - yes, in ruby) is more.
>
> Let's try (and why not start with ) the $7+ million alien punk #3100
> [1] with a headband? Anyone?
>
> Questions and comments welcome.
>
>
> [1] CryptoPunks: Details for Punk #3100
>
> Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
> <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
Andy Maleh

LinkedIn: https://www.linkedin.com/in/andymaleh
Blog: http://andymaleh.blogspot.com
GitHub: http://www.github.com/AndyObtiva

Sounds like something that you could use Cairographics/ RCairo for... though I admit I'm not clear on what's being tried.

But if you decide to look at Cairo/ RCairo, the official samples were ported to Ruby as:
https://notepad.onghu.com/2021/cairo-samples-in-ruby/

Best Regards,
Mohit.
2021-11-7 | 5:37 pm.

···

On 2021-11-6 9:11 pm, Gerald Bauer wrote:

Hello,

   here's the idea / the code golfing challenge:

  Let's draw 24x24 pixel punks with 2d graphics instruction e.g lineto,
moveto, etc. -
  thus, vectorizing pixel punks.

Hello,

    Thanks for highlighting the glimmer canvas api and the cairo 2d
graphics api - yes both look great.

though I admit I'm not clear on what's being tried

Let's draw 24x24 pixel punks with 2d graphics instruction e.g lineto,
moveto, etc. - thus, vectorizing pixel punks
[..]
Let's try (and why not start with ) the $7+ million alien punk #3100 [1]
with a headband? Anyone?

    Sorry for the confusion. To clear things up I put together a
"neutral" vectorized example
   of the alien punk #3100 in 24x24px format using scalable vector
graphics (SVG) that you read / display in a (web) browser:

    <svg width="24px" height="24px" xmlns="SVG namespace">

      <!-- face -->
      <path d="M 6 23 L 6 14 L 5 14 L 5 13 L 4 12
              L 5 11 L 6 11 L 6 7 L 7 6 L 8 5
              L 14 5 L 15 6 L 16 7 L 16 19 L 15 20 L 14 21 L 10
21 L 10 23"
            style="stroke: black; fill: #C8FBFB;" />

      <!-- mouth -->
      <path d="M 10 18 L 14 18"
           style="stroke: black; fill: none;" />

      <!-- nose -->
      <path d="M 12 16 L 12 14"
           style="stroke: #9BE0E0; fill: none;" />

      <!-- eyes -->
      <path d="M 9 13 L 9 12
              M 10 12 L 10 11
              M 14 13 L 14 12
               M 15 12 L 15 11"
            style="stroke: black; fill: none;" />
      <path d="M 10 13 L 10 12
              M 15 13 L 15 12"
            style="stroke: #9BE0E0; fill: none;" />
      <path d="M 9 12 L 9 11
              M 14 12 L 14 11"
            style="stroke: #75BDBD; fill: none;" />

      <!-- headband -->
      <path d="M 7 8 L 15 8" style="stroke: #1A6ED5; fill: none;" />
      <path d="M 7 7 L 15 7" style="stroke: white; fill: none;" />
    </svg>

  If you are new to (scalable) vector graphics - the M 7 8 L 15 8 -
for example,
reads as moveTo( 7, 8 ), lineTo( 15, 8 ) and so on in "long form".

   That's the idea to convert a 24x24px bitmap / pixel punk into a
vectorized version
  using 2D graphics drawing instructions - using your ruby (graphics)
library of choice. I hope that clears it up.

   Cheers. Prost.

[1] CryptoPunks: Details for Punk #3100

Hello,

The idea is to convert a 24x24px bitmap / pixel punk into a
vectorized version
using 2D graphics drawing instructions - using your ruby (graphics)
library of choice.

  As a follow-up here's a first try (yes, in ruby) using the vector
graphics support
in the pixelart gem:

    require 'pixelart'

    canvas = PixelArt::Vector.new( 24, 24 ) # 24 x 24 px

    ## face
    canvas.path( stroke: 'black', fill: '#c8fbfb' ).line(
                   6, 23, 6, 14,
                   5, 14, 5, 13, 4, 12,
                   5, 11, 6, 11, 6, 7, 7, 6, 8, 5,
                   14, 5, 15, 6,
                   16, 7, 16, 19, 15, 20, 14, 21, 10, 21, 10, 23 )

    ## mouth
    canvas.path( stroke: 'black' ).line( 10, 18, 14, 18 )
    ## nose
    canvas.path( stroke: '#9be0e0' ).line( 12, 16, 12, 14 )

    ## eyes
    canvas.path( stroke: 'black' ).line( 9, 13, 9, 12 )
                                  .line( 10, 12, 10, 11 )
                                  .line( 14, 13, 14, 12 )
                                  .line( 15, 12, 15, 11 )
    canvas.path( stroke: '#9be0e0' ).line( 10, 13, 10, 12 )
                                    .line( 15, 13, 15, 12 )
    canvas.path( stroke: '#75bdbd' ).line( 9, 12, 9, 11 )
                                    .line( 14, 12, 14, 11 )

    ## headband
    canvas.path( stroke: '#1a6ed5' ).line( 7, 8, 15, 8 )
    canvas.path( stroke: 'white' ).line( 7, 7, 15, 7 )

    canvas.save( "punk3100.svg" )

Can you do better? Let's "code golf" - that is - less (vector graphics
drawing instructions) is more.

Cheers. Prost.

PS: In round 1 - the idea is to draw a look-alike of the $7+ million
alien punk #3100 [1] with a headband.

[1] CryptoPunks: Details for Punk #3100

Hi Gerald,

I tried your example and I get this error when running it:

pixelart/1.2.0 on Ruby 3.0.2 (2021-07-07) [x86_64-darwin19] in
(/Users/andymaleh/.rvm/gems/ruby-3.0.2@cryptopunks-gui/gems/pixelart-1.2.0)

test1.rb:6:in `<main>': undefined method `path' for
#<Pixelart::Vector:0x00007fb820960ea8 @width=24, @height=24,
@header=nil, @shapes=[]> (NoMethodError)

Do I need to install anything in addition to the `pixelart` gem to make it work?

···

On Sat, Nov 13, 2021 at 9:13 AM Gerald Bauer <gerald.bauer@gmail.com> wrote:

Hello,

> The idea is to convert a 24x24px bitmap / pixel punk into a
> vectorized version
> using 2D graphics drawing instructions - using your ruby (graphics)
> library of choice.

  As a follow-up here's a first try (yes, in ruby) using the vector
graphics support
in the pixelart gem:

    require 'pixelart'

    canvas = PixelArt::Vector.new( 24, 24 ) # 24 x 24 px

    ## face
    canvas.path( stroke: 'black', fill: '#c8fbfb' ).line(
                   6, 23, 6, 14,
                   5, 14, 5, 13, 4, 12,
                   5, 11, 6, 11, 6, 7, 7, 6, 8, 5,
                   14, 5, 15, 6,
                   16, 7, 16, 19, 15, 20, 14, 21, 10, 21, 10, 23 )

    ## mouth
    canvas.path( stroke: 'black' ).line( 10, 18, 14, 18 )
    ## nose
    canvas.path( stroke: '#9be0e0' ).line( 12, 16, 12, 14 )

    ## eyes
    canvas.path( stroke: 'black' ).line( 9, 13, 9, 12 )
                                  .line( 10, 12, 10, 11 )
                                  .line( 14, 13, 14, 12 )
                                  .line( 15, 12, 15, 11 )
    canvas.path( stroke: '#9be0e0' ).line( 10, 13, 10, 12 )
                                    .line( 15, 13, 15, 12 )
    canvas.path( stroke: '#75bdbd' ).line( 9, 12, 9, 11 )
                                    .line( 14, 12, 14, 11 )

    ## headband
    canvas.path( stroke: '#1a6ed5' ).line( 7, 8, 15, 8 )
    canvas.path( stroke: 'white' ).line( 7, 7, 15, 7 )

    canvas.save( "punk3100.svg" )

Can you do better? Let's "code golf" - that is - less (vector graphics
drawing instructions) is more.

Cheers. Prost.

PS: In round 1 - the idea is to draw a look-alike of the $7+ million
alien punk #3100 [1] with a headband.

[1] CryptoPunks: Details for Punk #3100

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<ruby-talk list: member options login page>

--
Andy Maleh

LinkedIn: https://www.linkedin.com/in/andymaleh
Blog: http://andymaleh.blogspot.com
GitHub: AndyObtiva (Andy Maleh) · GitHub

Hello,

I tried your example and I get this error when running it:

pixelart/1.2.0 on Ruby 3.0.2 ...

   Thanks for trying. Sorry for the trouble - the vector path support
in the pixelart gem
is kind of a quick & dirty hack and addition - you have to upgrade to
latest and greatest version,
that is, v 1.2.1 - you are using 1.2.0 - almost up-to-date :-).

   All the best. Cheers. Prost.

Oh, nice! So, you added a DSL (an internal Domain Specific Language
embedded in Ruby)!?!! Sweet! You know, that idea originally comes from
LISP!! (yes, a programming language from the 60's). Here is a video
that explains what DSLs are all about in LISP and why they make
software engineering so productive and maintainable given LISP's
homoiconic data-as-code/code-as-data nature (never listen to a LISPer
who does not code DSLs as that would just be someone who sadly thinks
they're a LISP insider when in fact they are LISP outsiders who abuse
LISP by writing computer programs not the LISP way):

By the way, your canvas vertex graphics DSL looks so interestingly
similar to that of Glimmer DSL for LibUI (both consist of paths that
have lines and stroke/fill attributes). I just rewrote your code with
it with the addition of a zoom spinbox/slider to demonstrate my point
(see attached screenshot):

require 'glimmer-dsl-libui'

class CryptoPunksNumber3100
  include Glimmer

  ZOOM_MAX = 16

  def initialize
    @zoom = ZOOM_MAX
  end

  def launch
    window('CryptoPunks #3100', 24 * @zoom, 24 * @zoom + 100) {
      margined true

      vertical_box {
        horizontal_box {
          stretchy false

          label('Zoom:') {
            stretchy false
          }

          @zoom_spinbox = spinbox(1, ZOOM_MAX) {
            value ZOOM_MAX

            on_changed do
              @zoom = @zoom_spinbox.value
              @zoom_slider.value = @zoom
              @matrix.set_identity
              @matrix.scale(@zoom, @zoom)
              @area.queue_redraw_all
            end
          }
        }

        @zoom_slider = slider(1, ZOOM_MAX) {
          stretchy false
          value ZOOM_MAX

          on_changed do
            @zoom = @zoom_slider.value
            @zoom_spinbox.value = @zoom
            @matrix.set_identity
            @matrix.scale(@zoom, @zoom)
            @area.queue_redraw_all
          end
        }

        @area = area {
          ## face
          path {
            polyline(6, 23, 6, 14,
                     5, 14, 5, 13, 4, 12,
                     5, 11, 6, 11, 6, 7, 7, 6, 8, 5,
                     14, 5, 15, 6,
                     16, 7, 16, 19, 15, 20, 14, 21, 10, 21, 10, 23 )

            stroke 'black'
            fill 0xC8FBFB
          }

          ## mouth
          path {
            polyline(10, 18, 14, 18)

            stroke 'black'
          }

          ## nose
          path {
            polyline(12, 16, 12, 14)

            stroke 0x9BE0E0
          }

          ## eyes
          path {
            polyline(9, 13, 9, 12)
            polyline(10, 12, 10, 11)
            polyline(14, 13, 14, 12)
            polyline(15, 12, 15, 11)

            stroke 'black'
          }

          path {
            polyline( 10, 13, 10, 12 )
            polyline( 15, 13, 15, 12 )

            stroke 0x9BE0E0
          }

          path {
            polyline( 9, 12, 9, 11 )
            polyline( 14, 12, 14, 11 )

            stroke 0x75BDBD
          }

          ## headband
          path {
            polyline(7, 8, 15, 8)

            stroke 0x1A6ED5
          }

          path {
            polyline(7, 7, 15, 7)

            stroke 'white'
          }

          @matrix = matrix {
            scale ZOOM_MAX, ZOOM_MAX
          }
          transform @matrix
        }
      }
    }.show
  end
end

CryptoPunksNumber3100.new.launch

Glimmer's DSL is declarative and hierarchical just like LISP syntax,
but in Ruby's more elegant syntax, meaning it maps more closely to the
hierarchy of descriptive model nouns as they are envisioned (paths
containing lines with stroke/fill attributes) and is open to adding
programming statements around the attributes as opposed to having them
be stuffy and difficult to change inside hashes or method args.

In any case, my code above is perfect for changing and playing around
with the path shapes to come up with variations on that CryptoPunk
image #3100.

Cheers,

Andy Maleh

···

On Sat, Nov 13, 2021 at 3:46 PM Gerald Bauer <gerald.bauer@gmail.com> wrote:

Hello,

> I tried your example and I get this error when running it:
>
> pixelart/1.2.0 on Ruby 3.0.2 ...

   Thanks for trying. Sorry for the trouble - the vector path support
in the pixelart gem
is kind of a quick & dirty hack and addition - you have to upgrade to
latest and greatest version,
that is, v 1.2.1 - you are using 1.2.0 - almost up-to-date :-).

   All the best. Cheers. Prost.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<ruby-talk list: member options login page>

--
Andy Maleh

LinkedIn: https://www.linkedin.com/in/andymaleh
Blog: http://andymaleh.blogspot.com
GitHub: AndyObtiva (Andy Maleh) · GitHub

In any case, here is a variation that switches some polylines to
polybeziers, and adds some extra control points (see attachment for
image output):

require 'glimmer-dsl-libui'

class CryptoPunksNumber3100
  include Glimmer

  ZOOM_MAX = 16

  def initialize
    @zoom = ZOOM_MAX
  end

  def launch
    window('CryptoPunks #3100', 24 * @zoom, 24 * @zoom + 100) {
      margined true

      vertical_box {
        horizontal_box {
          stretchy false

          label('Zoom:') {
            stretchy false
          }

          @zoom_spinbox = spinbox(1, ZOOM_MAX) {
            value ZOOM_MAX

            on_changed do
              @zoom = @zoom_spinbox.value
              @zoom_slider.value = @zoom
              @matrix.set_identity
              @matrix.scale(@zoom, @zoom)
              @area.queue_redraw_all
            end
          }
        }

        @zoom_slider = slider(1, ZOOM_MAX) {
          stretchy false
          value ZOOM_MAX

          on_changed do
            @zoom = @zoom_slider.value
            @zoom_spinbox.value = @zoom
            @matrix.set_identity
            @matrix.scale(@zoom, @zoom)
            @area.queue_redraw_all
          end
        }

        @area = area {
          ## face
          path {
            polybezier(5, 24,
                     6, 23, 6, 14,
                     5, 14, 5, 13, 4, 12,
                     5, 11, 6, 11, 6, 7, 7, 6, 8, 5,
                     14, 5, 15, 6,
                     16, 7, 16, 19, 15, 20, 14, 21, 10, 21, 10, 23 )
            stroke 'black'
            fill 0xC8FBFB
          }

          ## mouth
          path {
            polybezier(10, 18, 12, 20, 12, 16, 14, 18)

            stroke 'black'
          }

          ## nose
          path {
            polybezier(12, 16, 14, 18, 10, 12, 12, 14)

            stroke 0x9BE0E0
          }

          ## eyes
          path {
            polyline(9, 13, 9, 12)
            polyline(10, 12, 10, 11)
            polyline(14, 13, 14, 12)
            polyline(15, 12, 15, 11)

            stroke 'black'
          }

          path {
            polyline( 10, 13, 10, 12 )
            polyline( 15, 13, 15, 12 )

            stroke 0x9BE0E0
          }

          path {
            polyline( 9, 12, 9, 11 )
            polyline( 14, 12, 14, 11 )

            stroke 0x75BDBD
          }

          ## headband
          path {
            polybezier(6, 9, 7, 8, 15, 8, 16, 7)

            stroke 0x1A6ED5
          }

          path {
            polybezier(6, 8, 7, 7, 15, 7, 16, 6)

            stroke 'white'
          }

          @matrix = matrix {
            scale ZOOM_MAX, ZOOM_MAX
          }
          transform @matrix
        }
      }
    }.show
  end
end

CryptoPunksNumber3100.new.launch

As you can see, I'm no artist! That's why I typically like to rely on
other graphic designers while I program their creations with code.

Andy

Sorry, but it looks like the wrong image got attached in my last
message. Here is another try.

CryptoPunks-3100-Crooked-Shrunk.png

I just submitted a Pull Request to Pixelart to document its new Canvas
Vector Graphics inline-DSL and to add a Glimmer DSL for Pixelart
alternative that is more declarative and hierarchical:

Here is another variation of Punk #3100 with a round nose:

![round nose](https://raw.githubusercontent.com/AndyObtiva/pixel/glimmerized/pixelart/i/punk3100-round-nose.svg)

Here is the Glimmerized code version (builds standard Punk #3100 and
then touches with a small change to make nose round):

require 'pixelart/glimmer'

include Glimmer # activates Glimmer DSL for Pixelart (in real apps,
mix into a class instead)

def face
  line {
    coordinates 6, 23,  6, 14,
                5, 14,  5, 13,  4, 12,
                5, 11,  6, 11,  6, 7,   7, 6,   8, 5,
                14, 5,  15, 6,
                16, 7,  16, 19,  15, 20,  14, 21,  10, 21, 10, 23
    fill '#c8fbfb'
    stroke 'black'
  }
end

def mouth
  line( 10, 18,  14, 18 ) {
    stroke 'black'
  }
end

def nose
  line( 12, 16,  12, 14 ) {
    stroke '#9be0e0'
  }
end

def eyes
  path {
    line( 9, 13,  9,  12 )
    line( 10, 12,  10, 11 )
    line( 14, 13,  14, 12 )
    line( 15, 12,  15, 11 )

    stroke 'black'
  }

  path {
    line( 10, 13,  10, 12 )
    line( 15, 13,  15, 12 )

    stroke '#9be0e0'
  }

  path {
    line( 9, 12,  9, 11 )
    line( 14, 12, 14, 11 )

    stroke '#75bdbd'
  }
end

def headband
  line( 7, 8, 15, 8 ) {
    stroke '#1a6ed5'
  }

  line( 7, 7,  15, 7 ) {
    stroke 'white'
  }
end

image = canvas(24, 24) {
  file './tmp/punk3100.svg' # auto-saves file when canvas expression closes

  face
  mouth
  nose
  eyes
  headband
}

# Re-open content to modify attributes and add more shapes
image.content {
  file './tmp/punk3100-round-nose.svg' # auto-saves file when content closes

  ## Round Nose
  circle( 12, 15,  1 ) {
    fill '#9be0e0'
  }
}
···

On Sun, Nov 14, 2021 at 4:35 PM Andy Maleh <andy.am@gmail.com> wrote:

Sorry, but it looks like the wrong image got attached in my last
message. Here is another try.

--
Andy Maleh

LinkedIn: https://www.linkedin.com/in/andymaleh
Blog: http://andymaleh.blogspot.com
GitHub: AndyObtiva (Andy Maleh) · GitHub