cryptopunks 1.2.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Manifest.txt +4 -20
- data/README.md +416 -34
- data/Rakefile +2 -1
- data/config/spritesheet.csv +762 -237
- data/config/spritesheet.png +0 -0
- data/lib/cryptopunks/composite.rb +3 -4
- data/lib/cryptopunks/contract/punksdata-assets.rb +338 -0
- data/lib/cryptopunks/contract/punksdata-contract.rb +55 -0
- data/lib/cryptopunks/contract/punksdata-meta.rb +2107 -0
- data/lib/cryptopunks/generator.rb +148 -43
- data/lib/cryptopunks/image.rb +4 -92
- data/lib/cryptopunks/tool.rb +381 -0
- data/lib/cryptopunks/version.rb +3 -3
- data/lib/cryptopunks.rb +113 -83
- metadata +21 -43
- data/config/more/alien-female.txt +0 -34
- data/config/more/ape-female.txt +0 -33
- data/config/more/demon-female.txt +0 -33
- data/config/more/demon-male.txt +0 -33
- data/config/more/mummy-female.txt +0 -33
- data/config/more/mummy-male.txt +0 -33
- data/config/more/orc-female.txt +0 -33
- data/config/more/orc-male.txt +0 -33
- data/config/more/robot-female.txt +0 -34
- data/config/more/robot-male.txt +0 -33
- data/config/more/skeleton-female.txt +0 -33
- data/config/more/skeleton-male.txt +0 -33
- data/config/more/vampire-female.txt +0 -33
- data/config/more/vampire-male.txt +0 -33
- data/config/more/zombie-female.txt +0 -33
- data/config/original/alien-male.txt +0 -33
- data/config/original/ape-male.txt +0 -33
- data/config/original/human-female.txt +0 -33
- data/config/original/human-male.txt +0 -34
- data/config/original/zombie-male.txt +0 -33
| @@ -5,23 +5,36 @@ module Cryptopunks | |
| 5 5 | 
             
                ### todo/fix:
         | 
| 6 6 | 
             
                ##    move into Punks::Metadata or such
         | 
| 7 7 | 
             
                class Sprite
         | 
| 8 | 
            -
                  attr_reader :id, :name, :type, :gender
         | 
| 8 | 
            +
                  attr_reader :id, :name, :type, :gender, :size, :more_names
         | 
| 9 9 |  | 
| 10 10 |  | 
| 11 11 | 
             
                  def initialize( id:,
         | 
| 12 12 | 
             
                                  name:,
         | 
| 13 13 | 
             
                                  type:,
         | 
| 14 | 
            -
                                  gender | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                     @ | 
| 18 | 
            -
                     @ | 
| 14 | 
            +
                                  gender:,
         | 
| 15 | 
            +
                                  size:,
         | 
| 16 | 
            +
                                  more_names: [] )
         | 
| 17 | 
            +
                     @id         = id      # zero-based index eg. 0,1,2,3, etc.
         | 
| 18 | 
            +
                     @name       = name
         | 
| 19 | 
            +
                     @type       = type
         | 
| 20 | 
            +
                     @gender     = gender
         | 
| 21 | 
            +
                     @size       = size
         | 
| 22 | 
            +
                     @more_names = more_names
         | 
| 19 23 | 
             
                  end
         | 
| 20 24 |  | 
| 21 25 | 
             
                  ## todo/check - find better names for type attribute/archetypes?
         | 
| 22 26 | 
             
                  ##     use (alternate name/alias) base or face  for archetypes? any others?
         | 
| 23 27 | 
             
                  def attribute?()  @type.downcase.start_with?( 'attribute' ); end
         | 
| 24 28 | 
             
                  def archetype?()  @type.downcase.start_with?( 'archetype' ); end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def small?()     @size == 's'; end
         | 
| 31 | 
            +
                  def large?()     @size == 'l'; end
         | 
| 32 | 
            +
                  def universal?() @size == 'u'; end
         | 
| 33 | 
            +
                  alias_method  :unisize?, :universal?   ## add unisize or allsizes or such - why? why not?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def male?()      @gender == 'm'; end
         | 
| 36 | 
            +
                  def female?()    @gender == 'f'; end
         | 
| 37 | 
            +
                  def unisex?()    @gender == 'u'; end
         | 
| 25 38 | 
             
               end  # class Metadata::Sprite
         | 
| 26 39 | 
             
              end # class Metadata
         | 
| 27 40 |  | 
| @@ -33,33 +46,60 @@ module Cryptopunks | |
| 33 46 | 
             
             ######
         | 
| 34 47 | 
             
             # static helpers  - (turn into "true" static self.class methods - why? why not?)
         | 
| 35 48 | 
             
             #
         | 
| 36 | 
            -
             def normalize_key( str )
         | 
| 37 | 
            -
             | 
| 49 | 
            +
             def self.normalize_key( str )
         | 
| 50 | 
            +
               ## add & e.g. B&W
         | 
| 51 | 
            +
                str.downcase.gsub(/[ ()&°_-]/, '').strip
         | 
| 38 52 | 
             
             end
         | 
| 39 53 |  | 
| 40 | 
            -
             def normalize_gender( str )
         | 
| 54 | 
            +
             def self.normalize_gender( str )
         | 
| 41 55 | 
             
                ## e.g. Female => f
         | 
| 42 56 | 
             
                ##      F => f
         | 
| 43 | 
            -
                ##  always return f | 
| 57 | 
            +
                ##  always return f/m
         | 
| 58 | 
            +
                str.downcase[0]
         | 
| 59 | 
            +
             end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
             def self.normalize_size( str )
         | 
| 62 | 
            +
                ## e.g. U or Unisize or Univeral => u
         | 
| 63 | 
            +
                ##      S or Small               => s
         | 
| 64 | 
            +
                ##      L or Large               => l
         | 
| 65 | 
            +
                ##   always return u/l/s
         | 
| 44 66 | 
             
                str.downcase[0]
         | 
| 45 67 | 
             
             end
         | 
| 46 68 |  | 
| 69 | 
            +
             def self.normalize_name( str )
         | 
| 70 | 
            +
               ## normalize spaces in more names
         | 
| 71 | 
            +
               str.strip.gsub( /[ ]{2,}/, ' ' )
         | 
| 72 | 
            +
             end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
             def normalize_key( str )     self.class.normalize_key( str ); end
         | 
| 75 | 
            +
             def normalize_gender( str )  self.class.normalize_gender( str ); end
         | 
| 76 | 
            +
             def normalize_size( str )    self.class.normalize_size( str ); end
         | 
| 77 | 
            +
             def normalize_name( str )    self.class.normalize_name( str ); end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
             | 
| 80 | 
            +
             | 
| 81 | 
            +
             | 
| 82 | 
            +
             | 
| 47 83 |  | 
| 48 84 | 
             
             def build_attributes_by_name( recs )
         | 
| 49 85 | 
             
                h = {}
         | 
| 50 86 | 
             
                recs.each_with_index do |rec|
         | 
| 51 | 
            -
                   | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                     | 
| 56 | 
            -
             | 
| 57 | 
            -
                     | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 87 | 
            +
                  names = [rec.name] + rec.more_names
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  names.each do |name|
         | 
| 90 | 
            +
                    key = normalize_key( name )
         | 
| 91 | 
            +
                    key << "_(#{rec.gender}+#{rec.size})"  if rec.attribute?
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    if h[ key ]
         | 
| 94 | 
            +
                      puts "!!! ERROR - attribute name is not unique:"
         | 
| 95 | 
            +
                      pp rec
         | 
| 96 | 
            +
                      puts "duplicate:"
         | 
| 97 | 
            +
                      pp h[key]
         | 
| 98 | 
            +
                      exit 1
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    h[ key ] = rec
         | 
| 60 101 | 
             
                  end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                end
         | 
| 102 | 
            +
               end
         | 
| 63 103 | 
             
                ## pp h
         | 
| 64 104 | 
             
                h
         | 
| 65 105 | 
             
             end
         | 
| @@ -83,16 +123,22 @@ module Cryptopunks | |
| 83 123 |  | 
| 84 124 | 
             
                ## convert to "wrapped / immutable" kind-of struct
         | 
| 85 125 | 
             
                recs = recs.map do |rec|
         | 
| 86 | 
            -
                         id | 
| 87 | 
            -
                         name | 
| 88 | 
            -
                         gender | 
| 89 | 
            -
                          | 
| 126 | 
            +
                         id         = rec['id'].to_i( 10 )
         | 
| 127 | 
            +
                         name       = normalize_name( rec['name'] )
         | 
| 128 | 
            +
                         gender     = normalize_gender( rec['gender'] )
         | 
| 129 | 
            +
                         size       = normalize_size( rec['size'] )
         | 
| 130 | 
            +
                         type       = rec['type']
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                         more_names = (rec['more_names'] || '').split( '|' )
         | 
| 133 | 
            +
                         more_names = more_names.map {|str| normalize_name( str ) }
         | 
| 90 134 |  | 
| 91 135 | 
             
                         Metadata::Sprite.new(
         | 
| 92 | 
            -
                           id: | 
| 93 | 
            -
                           name: | 
| 94 | 
            -
                           type: | 
| 95 | 
            -
                           gender: | 
| 136 | 
            +
                           id:         id,
         | 
| 137 | 
            +
                           name:       name,
         | 
| 138 | 
            +
                           type:       type,
         | 
| 139 | 
            +
                           gender:     gender,
         | 
| 140 | 
            +
                           size:       size,
         | 
| 141 | 
            +
                           more_names: more_names )
         | 
| 96 142 | 
             
                       end
         | 
| 97 143 | 
             
                recs
         | 
| 98 144 | 
             
             end  # method build_recs
         | 
| @@ -116,27 +162,80 @@ module Cryptopunks | |
| 116 162 | 
             
             alias_method  :sheet, :spritesheet
         | 
| 117 163 |  | 
| 118 164 |  | 
| 165 | 
            +
             def records() @recs; end
         | 
| 166 | 
            +
             alias_method :meta, :records
         | 
| 119 167 |  | 
| 120 168 |  | 
| 121 169 |  | 
| 122 | 
            -
             | 
| 170 | 
            +
             | 
| 171 | 
            +
             def find_meta( q, gender: nil,
         | 
| 172 | 
            +
                               size: nil,
         | 
| 173 | 
            +
                               style: nil )   ## note: gender (m/f) required for attributes!!!
         | 
| 123 174 |  | 
| 124 175 | 
             
                key = normalize_key( q )  ## normalize q(uery) string/symbol
         | 
| 125 | 
            -
                key << "_(#{normalize_gender( gender )})"  if gender
         | 
| 126 176 |  | 
| 127 | 
            -
                 | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
                    | 
| 177 | 
            +
                keys = []    ## note allow lookup by more than one keys
         | 
| 178 | 
            +
                                ##  e.g. if gender set try   f/m first and than try unisex as fallback
         | 
| 179 | 
            +
                if gender
         | 
| 180 | 
            +
                   gender = normalize_gender( gender )
         | 
| 181 | 
            +
                   ## auto-fill size if not passed in
         | 
| 182 | 
            +
                   ##    for f(emale)  =>   s(mall)
         | 
| 183 | 
            +
                   ##        m(ale)    =>   l(arge)
         | 
| 184 | 
            +
                   size =  if size.nil?
         | 
| 185 | 
            +
                             gender == 'f' ? 's' : 'l'
         | 
| 186 | 
            +
                           else
         | 
| 187 | 
            +
                             normalize_size( size )
         | 
| 188 | 
            +
                           end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                   ###
         | 
| 191 | 
            +
                   #  try (auto-add) style-specific version first (fallback to "regular" if not found)
         | 
| 192 | 
            +
                   if style
         | 
| 193 | 
            +
                     ## for now only support natural series
         | 
| 194 | 
            +
                     style_key =  if style.downcase.start_with?( 'natural' )
         | 
| 195 | 
            +
                                      'natural'
         | 
| 196 | 
            +
                                  else
         | 
| 197 | 
            +
                                    puts "!! ERROR - unknown attribute style #{style}; sorry"
         | 
| 198 | 
            +
                                    exit 1
         | 
| 199 | 
            +
                                  end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                     keys << "#{key}#{style_key}_(#{gender}+#{size})"
         | 
| 202 | 
            +
                     ## auto-add (u)niversal size as fallback
         | 
| 203 | 
            +
                     keys << "#{key}#{style_key}_(#{gender}+u)"  if size == 's' || size == 'l'
         | 
| 204 | 
            +
                     ## auto-add u(nisex) as fallback
         | 
| 205 | 
            +
                     keys << "#{key}#{style_key}_(u+#{size})"    if gender == 'f' || gender == 'm'
         | 
| 206 | 
            +
                   end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
             | 
| 209 | 
            +
                   keys << "#{key}_(#{gender}+#{size})"
         | 
| 210 | 
            +
                   ## auto-add (u)niversal size as fallback
         | 
| 211 | 
            +
                   keys << "#{key}_(#{gender}+u)"  if size == 's' || size == 'l'
         | 
| 212 | 
            +
                   ## auto-add u(nisex) as fallback
         | 
| 213 | 
            +
                   keys << "#{key}_(u+#{size})"    if gender == 'f' || gender == 'm'
         | 
| 131 214 | 
             
                else
         | 
| 132 | 
            -
                    | 
| 215 | 
            +
                   keys << key
         | 
| 133 216 | 
             
                end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
             | 
| 219 | 
            +
                rec = nil
         | 
| 220 | 
            +
                keys.each do |key|
         | 
| 221 | 
            +
                   rec = @attributes_by_name[ key ]
         | 
| 222 | 
            +
                   if rec
         | 
| 223 | 
            +
                     puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender}+#{rec.size})"
         | 
| 224 | 
            +
                     # pp rec
         | 
| 225 | 
            +
                     break
         | 
| 226 | 
            +
                   end
         | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                if rec.nil?
         | 
| 230 | 
            +
                   puts "!! WARN - no lookup found for #{keys.size} key(s) >#{keys.inspect}<"
         | 
| 231 | 
            +
                end
         | 
| 232 | 
            +
             | 
| 134 233 | 
             
                rec
         | 
| 135 234 | 
             
             end
         | 
| 136 235 |  | 
| 137 236 |  | 
| 138 | 
            -
             def find( q, gender: nil )  ## gender (m/f) required for attributes!!!
         | 
| 139 | 
            -
                rec = find_meta( q, gender: gender )
         | 
| 237 | 
            +
             def find( q, gender: nil, size: nil, style: nil )  ## gender (m/f) required for attributes!!!
         | 
| 238 | 
            +
                rec = find_meta( q, gender: gender, size: size, style: style )
         | 
| 140 239 |  | 
| 141 240 | 
             
                ## return image if record found
         | 
| 142 241 | 
             
                rec ? @sheet[ rec.id ] : nil
         | 
| @@ -145,10 +244,11 @@ module Cryptopunks | |
| 145 244 |  | 
| 146 245 |  | 
| 147 246 |  | 
| 148 | 
            -
             def to_recs( *values )
         | 
| 247 | 
            +
             def to_recs( *values, style: nil )
         | 
| 149 248 | 
             
                  archetype_name  = values[0]
         | 
| 150 249 |  | 
| 151 250 | 
             
                  ### todo/fix:  check for nil/not found!!!!
         | 
| 251 | 
            +
                  ## todo/check/fix:  assert meta record returned is archetype NOT attribute!!!!
         | 
| 152 252 | 
             
                  archetype  = find_meta( archetype_name )
         | 
| 153 253 | 
             
                  if archetype.nil?
         | 
| 154 254 | 
             
                    puts "!! ERROR -  archetype >#{archetype}< not found; sorry"
         | 
| @@ -160,11 +260,15 @@ module Cryptopunks | |
| 160 260 | 
             
                  attribute_names  = values[1..-1]
         | 
| 161 261 | 
             
                  ## note: attribute lookup requires gender from archetype!!!!
         | 
| 162 262 | 
             
                  attribute_gender = archetype.gender
         | 
| 263 | 
            +
                  attribute_size   = archetype.size
         | 
| 163 264 |  | 
| 164 265 | 
             
                  attribute_names.each do |attribute_name|
         | 
| 165 | 
            -
                    attribute = find_meta( attribute_name, | 
| 266 | 
            +
                    attribute = find_meta( attribute_name,
         | 
| 267 | 
            +
                                           gender: attribute_gender,
         | 
| 268 | 
            +
                                           size:   attribute_size,
         | 
| 269 | 
            +
                                           style:  style )
         | 
| 166 270 | 
             
                    if attribute.nil?
         | 
| 167 | 
            -
                       puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}) not found; sorry"
         | 
| 271 | 
            +
                       puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}+#{attribute_size}) not found; sorry"
         | 
| 168 272 | 
             
                       exit 1
         | 
| 169 273 | 
             
                    end
         | 
| 170 274 | 
             
                    recs << attribute
         | 
| @@ -176,12 +280,13 @@ module Cryptopunks | |
| 176 280 |  | 
| 177 281 |  | 
| 178 282 |  | 
| 179 | 
            -
             def generate_image( *values,  | 
| 283 | 
            +
             def generate_image( *values, style: nil,
         | 
| 284 | 
            +
                                          background: nil )
         | 
| 180 285 |  | 
| 181 286 | 
             
                ids = if values[0].is_a?( Integer )  ## assume integer number (indexes)
         | 
| 182 287 | 
             
                          values
         | 
| 183 288 | 
             
                      else ## assume strings (names)
         | 
| 184 | 
            -
                          to_recs( *values ).map { |rec| rec.id }
         | 
| 289 | 
            +
                          to_recs( *values, style: style ).map { |rec| rec.id }
         | 
| 185 290 | 
             
                      end
         | 
| 186 291 |  | 
| 187 292 |  | 
    
        data/lib/cryptopunks/image.rb
    CHANGED
    
    | @@ -1,45 +1,6 @@ | |
| 1 1 | 
             
            module Cryptopunks
         | 
| 2 2 |  | 
| 3 3 |  | 
| 4 | 
            -
            class Design   ## todo/fix - move to its own file!!!
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            end # class Design
         | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
            ##############
         | 
| 11 | 
            -
            ## todo/check:
         | 
| 12 | 
            -
            ##    find a better way to (auto?) include more designs?
         | 
| 13 | 
            -
            class DesignSeries    ## find a better name for class (just use Series?) - why? why not?
         | 
| 14 | 
            -
              def self.build( dir )
         | 
| 15 | 
            -
                data = {}
         | 
| 16 | 
            -
                paths =  Dir.glob( "#{dir}/**.txt" )
         | 
| 17 | 
            -
                paths.each do |path|
         | 
| 18 | 
            -
                  basename = File.basename( path, File.extname( path ) )
         | 
| 19 | 
            -
                  text = File.open( path, 'r:utf-8' ) { |f| f.read }
         | 
| 20 | 
            -
                  ## todo/check: auto-parse "ahead of time" here
         | 
| 21 | 
            -
                  ##              or keep "raw" text - why? why not?
         | 
| 22 | 
            -
                  data[ basename ] = text
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
                data
         | 
| 25 | 
            -
              end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
              def initialize( dir )
         | 
| 28 | 
            -
                @dir = dir  # e.g. "#{Cryptopunks.root}/config/more"
         | 
| 29 | 
            -
              end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
              def data
         | 
| 32 | 
            -
                ## note: lazy load / build on first demand only
         | 
| 33 | 
            -
                @data ||= self.class.build( @dir )
         | 
| 34 | 
            -
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              def [](key) data[ key ]; end
         | 
| 37 | 
            -
              def size()    data.size; end
         | 
| 38 | 
            -
              def keys()    data.keys; end
         | 
| 39 | 
            -
              def to_h()    data; end    ## todo/check: use to_hash() - why? why not?
         | 
| 40 | 
            -
            end   # class DesignSeries
         | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 4 |  | 
| 44 5 | 
             
            class Image
         | 
| 45 6 |  | 
| @@ -49,65 +10,16 @@ def self.read( path )   ## convenience helper | |
| 49 10 | 
             
            end
         | 
| 50 11 |  | 
| 51 12 |  | 
| 13 | 
            +
             | 
| 14 | 
            +
            ### keep design & colors keyword args in c'tor here
         | 
| 15 | 
            +
            ##     or use parse() like in pixelart - why? why not?
         | 
| 16 | 
            +
             | 
| 52 17 | 
             
            def initialize( initial=nil, design: nil,
         | 
| 53 18 | 
             
                                         colors: nil )
         | 
| 54 19 | 
             
                if initial
         | 
| 55 20 | 
             
                  ## pass image through as-is
         | 
| 56 21 | 
             
                  img = initial
         | 
| 57 22 | 
             
                else
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                  ## todo/fix:
         | 
| 60 | 
            -
                  ## move design code into design class!!!
         | 
| 61 | 
            -
                  ##  for now assume design is a string
         | 
| 62 | 
            -
                  ##   split into parts
         | 
| 63 | 
            -
                  ##       original/alien-male or original@alien-male
         | 
| 64 | 
            -
                  ##       more/alien-female or more@alien-female
         | 
| 65 | 
            -
                  ##       original/human-male+darker or original@human-male!darker ????
         | 
| 66 | 
            -
                  ##        human-male!darker ?????
         | 
| 67 | 
            -
                  ##    keep @ as separator too - why? why not?
         | 
| 68 | 
            -
                  parts = design.split( %r{[@/]} )
         | 
| 69 | 
            -
                  parts.unshift( '*' )    if parts.size == 1  ## assume "all-in-one" series (use * as name/id/placeholder)
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                  series_key        = parts[0]
         | 
| 72 | 
            -
                  design_composite  = parts[1]
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                  ## todo/check - find a way for unambigious (color) variant key
         | 
| 75 | 
            -
                  ##   use unique char e.g. +*!# or such
         | 
| 76 | 
            -
                  more_parts = design_composite.split(  %r{[!+]} )
         | 
| 77 | 
            -
                  design_key   = more_parts[0]
         | 
| 78 | 
            -
                  variant_key  = more_parts[1]     ## color variant for now (for humans) e.g. lighter/light/dark/darker
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                  series = if ['*','**','_','__'].include?( series_key )
         | 
| 81 | 
            -
                              DESIGNS    ## use all-series-in-one collection
         | 
| 82 | 
            -
                           else
         | 
| 83 | 
            -
                              case series_key
         | 
| 84 | 
            -
                              when 'original' then DESIGNS_ORIGINAL
         | 
| 85 | 
            -
                              when 'more'     then DESIGNS_MORE
         | 
| 86 | 
            -
                              else  raise ArgumentError, "unknown design series >#{series_key}<; sorry"
         | 
| 87 | 
            -
                              end
         | 
| 88 | 
            -
                           end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                  design = series[ design_key ]
         | 
| 91 | 
            -
                  raise ArgumentError, "unknow design >#{design_key}< in series >#{series_key}<; sorry"  if design.nil?
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                  if colors.nil?  ## try to auto-fill in colors
         | 
| 94 | 
            -
                     ##  note: (auto-)remove _male,_female qualifier if exist
         | 
| 95 | 
            -
                     colors_key = design_key.sub( '-male', '' ).sub( '-female', '' )
         | 
| 96 | 
            -
                     colors =  COLORS[ colors_key ]
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                     ## allow / support color scheme variants (e.g. lighter/light/dark/darker) etc.
         | 
| 99 | 
            -
                     if colors.is_a?(Hash)
         | 
| 100 | 
            -
                       if variant_key
         | 
| 101 | 
            -
                         colors = colors[ variant_key ]
         | 
| 102 | 
            -
                         raise ArgumentError, "no colors defined for variant >#{variant_key}< for design >#{design_key}< in series >#{series_key}<; sorry"   if colors.nil?
         | 
| 103 | 
            -
                       else  ## note: use (fallback to) first color scheme if no variant key present
         | 
| 104 | 
            -
                         colors = colors[ colors.keys[0] ]
         | 
| 105 | 
            -
                       end
         | 
| 106 | 
            -
                     end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                     raise ArgumentError, "no (default) colors defined for design >#{design_key}< in series >#{series_key}<; sorry"   if colors.nil?
         | 
| 109 | 
            -
                  end
         | 
| 110 | 
            -
             | 
| 111 23 | 
             
                  ## note: unwrap inner image before passing on to super c'tor
         | 
| 112 24 | 
             
                  img = Pixelart::Image.parse( design, colors: colors ).image
         | 
| 113 25 | 
             
                end
         |