diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87237d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Gladiator (Glimmer Editor) +.gladiator +.DS_Store diff --git a/pixelart/.gitignore b/pixelart/.gitignore index 38b7fd8..0d3f038 100644 --- a/pixelart/.gitignore +++ b/pixelart/.gitignore @@ -28,9 +28,12 @@ build/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # Gemfile.lock -# .ruby-version -# .ruby-gemset +.ruby-version +.ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc +# Gladiator (Glimmer Editor) +.gladiator +.DS_Store diff --git a/pixelart/README.md b/pixelart/README.md index 96fb4d0..4c4c092 100644 --- a/pixelart/README.md +++ b/pixelart/README.md @@ -277,6 +277,148 @@ require 'pixelart/base' +## Canvas Vector Graphics + +You may utilize inline-DSL (Domain Specific Language) syntax for building vector graphics via a canvas `PixelArt::Vector` object: + +```ruby +require 'pixelart/base' + + +canvas = PixelArt::Vector.new( 24, 24 ) + +## 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( "./tmp/punk3100.svg" ) +``` + +Voila! + + + +## Modular "Glimmer" Version + +You may utilize a [Glimmer](https://github.com/AndyObtiva/glimmer) DSL for Pixelart vector graphics by requiring `'pixelart/glimmer'` and including `Glimmer` module: + +```ruby +require 'pixelart/glimmer' +include Glimmer # activates Glimmer DSL for Pixelart (in real apps, mix into a class instead) +``` + +Afterwards, you may build canvas vector graphics declaratively and hierarchically: + +```ruby +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' + } +} +``` + +Voila! + + + ## Install Just install the gem: @@ -288,4 +430,3 @@ Just install the gem: The scripts are dedicated to the public domain. Use it as you please with no restrictions whatsoever. - diff --git a/pixelart/i/punk3100-round-nose.svg b/pixelart/i/punk3100-round-nose.svg new file mode 100644 index 0000000..f1db205 --- /dev/null +++ b/pixelart/i/punk3100-round-nose.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pixelart/i/punk3100.svg b/pixelart/i/punk3100.svg new file mode 100644 index 0000000..6356c83 --- /dev/null +++ b/pixelart/i/punk3100.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pixelart/lib/glimmer/dsl/pixelart/attribute_expression.rb b/pixelart/lib/glimmer/dsl/pixelart/attribute_expression.rb new file mode 100644 index 0000000..6674b55 --- /dev/null +++ b/pixelart/lib/glimmer/dsl/pixelart/attribute_expression.rb @@ -0,0 +1,17 @@ +require 'glimmer/dsl/expression' + +module Glimmer + module DSL + module Pixelart + class AttributeExpression < Expression + def can_interpret?(parent, keyword, *args, &block) + parent.respond_to?("#{keyword}=") && !args.empty? && block.nil? + end + + def interpret(parent, keyword, *args, &block) + parent.send("#{keyword}=", *args) + end + end + end + end +end diff --git a/pixelart/lib/glimmer/dsl/pixelart/dsl.rb b/pixelart/lib/glimmer/dsl/pixelart/dsl.rb new file mode 100644 index 0000000..22817d1 --- /dev/null +++ b/pixelart/lib/glimmer/dsl/pixelart/dsl.rb @@ -0,0 +1,16 @@ +require 'glimmer/dsl/engine' +Dir[File.expand_path('*_expression.rb', __dir__)].each {|f| require f} + +module Glimmer + module DSL + module Pixelart + Engine.add_dynamic_expressions( + Pixelart, + %w[ + attribute + element + ] + ) + end + end +end diff --git a/pixelart/lib/glimmer/dsl/pixelart/element_expression.rb b/pixelart/lib/glimmer/dsl/pixelart/element_expression.rb new file mode 100644 index 0000000..99cc3a1 --- /dev/null +++ b/pixelart/lib/glimmer/dsl/pixelart/element_expression.rb @@ -0,0 +1,28 @@ +require 'glimmer/dsl/expression' +require 'glimmer/dsl/parent_expression' +require 'glimmer/dsl/top_level_expression' +require_relative '../../pixelart/element' + +module Glimmer + module DSL + module Pixelart + class ElementExpression < Expression + include ParentExpression + include TopLevelExpression + + def can_interpret?(parent, keyword, *args, &block) + Glimmer::Pixelart::Element.element_exist?(keyword) + end + + def interpret(parent, keyword, *args, &block) + Glimmer::Pixelart::Element.element_class(keyword).new(parent, keyword, *args, &block) + end + + def add_content(element, keyword, *args, &block) + super + element.post_add_content + end + end + end + end +end diff --git a/pixelart/lib/glimmer/pixelart/element.rb b/pixelart/lib/glimmer/pixelart/element.rb new file mode 100644 index 0000000..50c1645 --- /dev/null +++ b/pixelart/lib/glimmer/pixelart/element.rb @@ -0,0 +1,53 @@ +require 'facets/string/camelcase' + +module Glimmer + module Pixelart + class Element + class << self + def element_exist?(keyword) + constants.include?(element_class_name(keyword)) && element_class(keyword).respond_to?(:new) + end + + def element_class(keyword) + const_get(element_class_name(keyword)) + end + + def element_class_name(keyword) + keyword.to_s.camelcase(:upper).to_sym + end + end + + attr_reader :parent, :keyword, :args, :block + + def initialize(parent, keyword, *args, &block) + @parent = parent + @keyword = keyword + @args = args + @block = block + @children = [] + @parent&.post_initialize_child(self) + post_add_content if @block.nil? + end + + # Subclasses may optionally override and call super to have children built + def build + @children&.each(&:build) + end + + def post_initialize_child(child) + @children << child + end + + def post_add_content + # No Op (subclasses may override to do something at the closing of the element) + end + + # Enables re-opening content and adding new shapes + def content(&block) + Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Pixelart::ElementExpression.new, @keyword, &block) + end + end + end +end + +Dir[File.expand_path('./element/*.rb', __dir__)].each {|f| require f} diff --git a/pixelart/lib/glimmer/pixelart/element/canvas.rb b/pixelart/lib/glimmer/pixelart/element/canvas.rb new file mode 100644 index 0000000..e45e027 --- /dev/null +++ b/pixelart/lib/glimmer/pixelart/element/canvas.rb @@ -0,0 +1,50 @@ +require 'pixelart/base' + +module Glimmer + module Pixelart + class Element + class Canvas < Element + attr_reader :vector + attr_accessor :file + + def width + @args[0] + end + + def height + @args[1] + end + + def width=(new_width) + @args[0] = new_width + end + + def height=(new_height) + @args[1] = new_height + end + + def post_add_content + build + end + + def build + @vector = ::PixelArt::Vector.new(*@args) + super + @vector.save(@file) if @file + end + + def respond_to?(method_name, include_private = true) + super || @vector.respond_to?(method_name, include_private) + end + + def method_missing(method_name, *args, **kwargs, &block) + if @vector.respond_to?(method_name, true) + @vector.send(method_name, *args, **kwargs, &block) + else + super + end + end + end + end + end +end diff --git a/pixelart/lib/glimmer/pixelart/element/circle.rb b/pixelart/lib/glimmer/pixelart/element/circle.rb new file mode 100644 index 0000000..5d383f6 --- /dev/null +++ b/pixelart/lib/glimmer/pixelart/element/circle.rb @@ -0,0 +1,43 @@ +module Glimmer + module Pixelart + class Element + class Circle < Element + attr_accessor :fill + + def cx + @args[0] + end + alias center_x cx + + def cy + @args[1] + end + alias center_y cy + + def r + @args[2] + end + alias radius r + + def cx=(new_cx) + @args[0] = new_cx + end + alias center_x cx + + def cy=(new_cy) + @args[1] = new_cy + end + alias center_y cy + + def r=(new_r) + @args[2] = new_r + end + alias radius r + + def build + @parent.circle(cx: cx, cy: cy, r: r, fill: @fill) + end + end + end + end +end diff --git a/pixelart/lib/glimmer/pixelart/element/line.rb b/pixelart/lib/glimmer/pixelart/element/line.rb new file mode 100644 index 0000000..e6aa734 --- /dev/null +++ b/pixelart/lib/glimmer/pixelart/element/line.rb @@ -0,0 +1,27 @@ +module Glimmer + module Pixelart + class Element + class Line < Element + attr_accessor :fill, :stroke + + def coordinates + @args + end + + def coordinates=(*new_coordinates) + new_coordinates = new_coordinates.first if new_coordinates.size == 1 && new_coordinates.first.is_a?(Array) + @args = *new_coordinates + end + + def build + case @parent + when Glimmer::Pixelart::Element::Canvas + @parent.path(stroke: @stroke, fill: @fill).line(*coordinates) + when Glimmer::Pixelart::Element::Path + @parent.parent.path(stroke: @parent.stroke, fill: @parent.fill).line(*coordinates) + end + end + end + end + end +end diff --git a/pixelart/lib/glimmer/pixelart/element/path.rb b/pixelart/lib/glimmer/pixelart/element/path.rb new file mode 100644 index 0000000..f90d99b --- /dev/null +++ b/pixelart/lib/glimmer/pixelart/element/path.rb @@ -0,0 +1,9 @@ +module Glimmer + module Pixelart + class Element + class Path < Element + attr_accessor :fill, :stroke + end + end + end +end diff --git a/pixelart/lib/pixelart/glimmer.rb b/pixelart/lib/pixelart/glimmer.rb new file mode 100644 index 0000000..66e7335 --- /dev/null +++ b/pixelart/lib/pixelart/glimmer.rb @@ -0,0 +1,3 @@ +require 'glimmer' + +require_relative '../glimmer/dsl/pixelart/dsl' diff --git a/pixelart/sandbox/test_glimmer.rb b/pixelart/sandbox/test_glimmer.rb new file mode 100644 index 0000000..bd545fb --- /dev/null +++ b/pixelart/sandbox/test_glimmer.rb @@ -0,0 +1,88 @@ +### +# to run use +# ruby -I ./lib sandbox/test_vector.rb + + +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' + } +} diff --git a/pixelart/sandbox/test_vector.rb b/pixelart/sandbox/test_vector.rb index 5e56b59..88cbae6 100644 --- a/pixelart/sandbox/test_vector.rb +++ b/pixelart/sandbox/test_vector.rb @@ -38,5 +38,3 @@ canvas.save( "./tmp/punk3100.svg" ) - -