pixelart 0.1.2 → 0.1.7
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 +6 -0
- data/README.md +55 -11
- data/Rakefile +1 -1
- data/lib/pixelart.rb +6 -20
- data/lib/pixelart/base.rb +36 -0
- data/lib/pixelart/color.rb +132 -0
- data/lib/pixelart/gradient.rb +106 -0
- data/lib/pixelart/image.rb +61 -65
- data/lib/pixelart/misc.rb +37 -0
- data/lib/pixelart/palette.rb +72 -0
- data/lib/pixelart/pixelator.rb +117 -0
- data/lib/pixelart/version.rb +1 -1
- metadata +9 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0dd959d7ee982c2634f325c6e7f91bfb46d12dc61a79f364d0b7caae529d59f1
         | 
| 4 | 
            +
              data.tar.gz: 024f66e021b395b4141c54298e2fb0f31b47d28952a5c03d56ecfaf75c68b1be
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 71093d3b9977140303838bf9628c1de54c47f978a556945321fd849d3ebb26912d50846050c15efe3f1a2cf4d8b0991940c08f4a63511451148f63d2b9e788f5
         | 
| 7 | 
            +
              data.tar.gz: 41c97279da0b7f81430a56c1c8e88be1ebc2bac61ea6b7a136be1accfe3cd71b7c5e0c3885d3a7dfa5d021852d4550e3f692b151767a18f6fcf7a8c9fd5340d0
         | 
    
        data/Manifest.txt
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -3,8 +3,8 @@ | |
| 3 3 | 
             
            pixelart  - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes
         | 
| 4 4 |  | 
| 5 5 |  | 
| 6 | 
            -
            * home  :: [github.com/ | 
| 7 | 
            -
            * bugs  :: [github.com/ | 
| 6 | 
            +
            * home  :: [github.com/rubycoco/pixel](https://github.com/rubycoco/pixel)
         | 
| 7 | 
            +
            * bugs  :: [github.com/rubycoco/pixel/issues](https://github.com/rubycoco/pixel/issues)
         | 
| 8 8 | 
             
            * gem   :: [rubygems.org/gems/pixelart](https://rubygems.org/gems/pixelart)
         | 
| 9 9 | 
             
            * rdoc  :: [rubydoc.info/gems/pixelart](http://rubydoc.info/gems/pixelart)
         | 
| 10 10 |  | 
| @@ -58,7 +58,7 @@ Note: The color 0 (transparent) is auto-magically added / defined. | |
| 58 58 | 
             
            And let's mint a mooncat image:
         | 
| 59 59 |  | 
| 60 60 | 
             
            ``` ruby
         | 
| 61 | 
            -
            img =  | 
| 61 | 
            +
            img = Image.parse( pixels, colors: colors )
         | 
| 62 62 | 
             
            img.save( './i/mooncat_white.png' )
         | 
| 63 63 | 
             
            ```
         | 
| 64 64 |  | 
| @@ -71,8 +71,8 @@ img3x.save( './i/mooncat_white-3x.png' ) | |
| 71 71 |  | 
| 72 72 | 
             
            Voila!
         | 
| 73 73 |  | 
| 74 | 
            -
            
         | 
| 75 | 
            +
            
         | 
| 76 76 |  | 
| 77 77 |  | 
| 78 78 |  | 
| @@ -91,7 +91,7 @@ colors = [ | |
| 91 91 | 
             
            And let's start minting:
         | 
| 92 92 |  | 
| 93 93 | 
             
            ``` ruby
         | 
| 94 | 
            -
            img =  | 
| 94 | 
            +
            img = Image.parse( pixels, colors: colors )
         | 
| 95 95 | 
             
            img.save( './i/mooncat_black.png' )
         | 
| 96 96 |  | 
| 97 97 | 
             
            img3x = img.zoom( 3 )
         | 
| @@ -100,8 +100,8 @@ img3x.save( './i/mooncat_black-3x.png' ) | |
| 100 100 |  | 
| 101 101 | 
             
            Voila! Black is the new White!
         | 
| 102 102 |  | 
| 103 | 
            -
            
         | 
| 104 | 
            +
            
         | 
| 105 105 |  | 
| 106 106 |  | 
| 107 107 |  | 
| @@ -214,7 +214,7 @@ colors = { | |
| 214 214 | 
             
            And let's mint an imperial master image:
         | 
| 215 215 |  | 
| 216 216 | 
             
            ``` ruby
         | 
| 217 | 
            -
            img =  | 
| 217 | 
            +
            img = Image.parse( pixels, colors: colors )
         | 
| 218 218 | 
             
            img.save( './i/vader.png' )
         | 
| 219 219 | 
             
            ```
         | 
| 220 220 |  | 
| @@ -227,8 +227,52 @@ img5x.save( './i/vader5x.png' ) | |
| 227 227 |  | 
| 228 228 | 
             
            Voila!
         | 
| 229 229 |  | 
| 230 | 
            -
            
         | 
| 231 | 
            +
            
         | 
| 232 | 
            +
             | 
| 233 | 
            +
             | 
| 234 | 
            +
             | 
| 235 | 
            +
             | 
| 236 | 
            +
            ## Modular "Base" Version
         | 
| 237 | 
            +
             | 
| 238 | 
            +
             | 
| 239 | 
            +
            Note: By default if you require pixelart
         | 
| 240 | 
            +
            all classes inside the `Pixelart` module such as `Image`, `Color`, `Gradient`, `Palette8bit`, etc. get "top-leveled", that is,
         | 
| 241 | 
            +
            included in the top level e.g.:
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            ``` ruby
         | 
| 244 | 
            +
            require 'pixelart/base'
         | 
| 245 | 
            +
            include Pixelart
         | 
| 246 | 
            +
            ```
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            And now you can use all classes without
         | 
| 249 | 
            +
            the `Pixelart::` module scope e.g.:
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            ``` ruby
         | 
| 252 | 
            +
            gradient = Gradient.new( '000000', 'ffffff' )
         | 
| 253 | 
            +
             | 
| 254 | 
            +
            pp colors = gradient.colors( 256 )
         | 
| 255 | 
            +
            puts '---'
         | 
| 256 | 
            +
            pp colors.map { |color| Color.to_hex( color ) }
         | 
| 257 | 
            +
            ```
         | 
| 258 | 
            +
             | 
| 259 | 
            +
            vs
         | 
| 260 | 
            +
             | 
| 261 | 
            +
            ``` ruby
         | 
| 262 | 
            +
            gradient = Pixelart::Gradient.new( '000000', 'ffffff' )
         | 
| 263 | 
            +
             | 
| 264 | 
            +
            pp colors = gradient.colors( 256 )
         | 
| 265 | 
            +
            puts '---'
         | 
| 266 | 
            +
            pp colors.map { |color| Pixelart::Color.to_hex( color ) }
         | 
| 267 | 
            +
            ```
         | 
| 268 | 
            +
             | 
| 269 | 
            +
             | 
| 270 | 
            +
            For a "stricter" modular version require the "base" version
         | 
| 271 | 
            +
            that always requires the `Pixelart::` module scope e.g.:
         | 
| 272 | 
            +
             | 
| 273 | 
            +
            ``` ruby
         | 
| 274 | 
            +
            require 'pixelart/base'
         | 
| 275 | 
            +
            ```
         | 
| 232 276 |  | 
| 233 277 |  | 
| 234 278 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -8,7 +8,7 @@ Hoe.spec 'pixelart' do | |
| 8 8 | 
             
              self.summary = "pixelart - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes"
         | 
| 9 9 | 
             
              self.description = summary
         | 
| 10 10 |  | 
| 11 | 
            -
              self.urls    = { home: 'https://github.com/ | 
| 11 | 
            +
              self.urls    = { home: 'https://github.com/rubycoco/pixel' }
         | 
| 12 12 |  | 
| 13 13 | 
             
              self.author  = 'Gerald Bauer'
         | 
| 14 14 | 
             
              self.email   = 'wwwmake@googlegroups.com'
         | 
    
        data/lib/pixelart.rb
    CHANGED
    
    | @@ -1,24 +1,10 @@ | |
| 1 | 
            -
            ## 3rd party
         | 
| 2 | 
            -
            require 'chunky_png'
         | 
| 3 1 |  | 
| 4 | 
            -
            ##  | 
| 5 | 
            -
            require ' | 
| 6 | 
            -
            require 'time'
         | 
| 7 | 
            -
            require 'date'
         | 
| 8 | 
            -
            require 'fileutils'
         | 
| 2 | 
            +
            ## our own code (without "top-level" shortcuts e.g. "modular version")
         | 
| 3 | 
            +
            require 'pixelart/base'   # aka "strict(er)" version
         | 
| 9 4 |  | 
| 10 5 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 6 | 
            +
            ###
         | 
| 7 | 
            +
            #  add convenience top-level shortcuts / aliases
         | 
| 8 | 
            +
            #    make Image, Color, Palette8bit, etc top-level
         | 
| 9 | 
            +
            include Pixelart
         | 
| 14 10 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
            ### add some convenience shortcuts
         | 
| 20 | 
            -
            PixelArt = Pixelart
         | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
            puts Pixelart.banner    # say hello
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            ## 3rd party
         | 
| 2 | 
            +
            require 'chunky_png'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ## stdlib
         | 
| 5 | 
            +
            require 'pp'
         | 
| 6 | 
            +
            require 'time'
         | 
| 7 | 
            +
            require 'date'
         | 
| 8 | 
            +
            require 'fileutils'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            ## our own code
         | 
| 12 | 
            +
            require 'pixelart/version'    # note: let version always go first
         | 
| 13 | 
            +
            require 'pixelart/color'
         | 
| 14 | 
            +
            require 'pixelart/gradient'
         | 
| 15 | 
            +
            require 'pixelart/palette'
         | 
| 16 | 
            +
            require 'pixelart/image'
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            require 'pixelart/pixelator'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            require 'pixelart/misc'   ## misc helpers
         | 
| 21 | 
            +
             | 
| 22 | 
            +
             | 
| 23 | 
            +
            ##########
         | 
| 24 | 
            +
            #  add some spelling convenience variants
         | 
| 25 | 
            +
            PixelArt = Pixelart
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            module Pixelart
         | 
| 28 | 
            +
              Palette256 = Palette8Bit = Palette8bit
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              Palette256Image = Palette8BitImage = Palette8bitImage =
         | 
| 31 | 
            +
              ImagePalette256 = ImagePalette8Bit = ImagePalette8bit
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
             | 
| 35 | 
            +
             | 
| 36 | 
            +
            puts Pixelart.banner    # say hello
         | 
| @@ -0,0 +1,132 @@ | |
| 1 | 
            +
            module Pixelart
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Color
         | 
| 5 | 
            +
              TRANSPARENT = 0            # rgba(  0,  0,  0,  0)
         | 
| 6 | 
            +
              BLACK       = 0xff         # rgba(  0,  0,  0,255)
         | 
| 7 | 
            +
              WHITE       = 0xffffffff   # rgba(255,255,255,255)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
              def self.parse( color )
         | 
| 12 | 
            +
                if color.is_a?( Integer )  ## e.g. assumes ChunkyPNG::Color.rgb() or such
         | 
| 13 | 
            +
                  color ## pass through as is 1:1
         | 
| 14 | 
            +
                elsif color.is_a?( Array )  ## assume array of hsl(a) e. g. [180, 0.86, 0.88]
         | 
| 15 | 
            +
                  from_hsl( *color )
         | 
| 16 | 
            +
                elsif color.is_a?( String )
         | 
| 17 | 
            +
                  if color.downcase == 'transparent'   ## special case for builtin colors
         | 
| 18 | 
            +
                    TRANSPARENT
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    ## note: return an Integer !!! (not a Color class or such!!! )
         | 
| 21 | 
            +
                    from_hex( color )
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                else
         | 
| 24 | 
            +
                  raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def self.from_hex( hex )
         | 
| 29 | 
            +
                ## Creates a color by converting it from a string in hex notation.
         | 
| 30 | 
            +
                ##
         | 
| 31 | 
            +
                ## It supports colors with (#rrggbbaa) or without (#rrggbb)
         | 
| 32 | 
            +
                ##  alpha channel as well as the 3-digit short format (#rgb)
         | 
| 33 | 
            +
                ## for those without. Color strings may include
         | 
| 34 | 
            +
                ## the prefix "0x" or "#"".
         | 
| 35 | 
            +
                ChunkyPNG::Color.from_hex( hex )
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def self.from_hsl( hue, saturation, lightness, alpha=255)
         | 
| 39 | 
            +
                ChunkyPNG::Color.from_hsl( hue,
         | 
| 40 | 
            +
                                           saturation,
         | 
| 41 | 
            +
                                           lightness,
         | 
| 42 | 
            +
                                           alpha )
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
             | 
| 46 | 
            +
              def self.to_hex( color, include_alpha: true )
         | 
| 47 | 
            +
                ChunkyPNG::Color.to_hex( color, include_alpha )
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def self.to_hsl( color, include_alpha: true )
         | 
| 51 | 
            +
                # Returns an array with the separate HSL components of a color.
         | 
| 52 | 
            +
                ChunkyPNG::Color.to_hsl( color, include_alpha )
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def self.r( color ) ChunkyPNG::Color.r( color ); end
         | 
| 56 | 
            +
              def self.g( color ) ChunkyPNG::Color.g( color ); end
         | 
| 57 | 
            +
              def self.b( color ) ChunkyPNG::Color.b( color ); end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def self.rgb( r, g, b ) ChunkyPNG::Color.rgb( r, g, b); end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
             | 
| 62 | 
            +
             | 
| 63 | 
            +
              ## known built-in color names
         | 
| 64 | 
            +
              def self.build_names
         | 
| 65 | 
            +
                names = {
         | 
| 66 | 
            +
                  '#00000000' => 'TRANSPARENT',
         | 
| 67 | 
            +
                  '#000000ff' => 'BLACK',
         | 
| 68 | 
            +
                  '#ffffffff' => 'WHITE',
         | 
| 69 | 
            +
                }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                ## auto-add grayscale 1 to 254
         | 
| 72 | 
            +
                (1..254).each do |n|
         | 
| 73 | 
            +
                  hex = "#" + ('%02x' % n)*3
         | 
| 74 | 
            +
                  hex << "ff"  ## add alpha channel (255)
         | 
| 75 | 
            +
                  names[ hex ] = "8-BIT GRAYSCALE ##{n}"
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                names
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              NAMES = build_names
         | 
| 82 | 
            +
             | 
| 83 | 
            +
             | 
| 84 | 
            +
             | 
| 85 | 
            +
              def self.format( color )
         | 
| 86 | 
            +
                rgb = [r(color),
         | 
| 87 | 
            +
                       g(color),
         | 
| 88 | 
            +
                       b(color)]
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                # rgb in hex (string format)
         | 
| 91 | 
            +
                #   note: do NOT include alpha channel for now - why? why not?
         | 
| 92 | 
            +
                hex = "#" + rgb.map{|num| '%02x' % num }.join
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                hsl = to_hsl( color )
         | 
| 95 | 
            +
                ## get alpha channel (transparency) for hsla
         | 
| 96 | 
            +
                alpha = hsl[3]
         | 
| 97 | 
            +
             | 
| 98 | 
            +
             | 
| 99 | 
            +
                buf = ''
         | 
| 100 | 
            +
                buf << hex
         | 
| 101 | 
            +
                buf << " / "
         | 
| 102 | 
            +
                buf << "rgb("
         | 
| 103 | 
            +
                buf << "%3d " % rgb[0]
         | 
| 104 | 
            +
                buf << "%3d " % rgb[1]
         | 
| 105 | 
            +
                buf << "%3d)"  % rgb[2]
         | 
| 106 | 
            +
                buf << " - "
         | 
| 107 | 
            +
                buf << "hsl("
         | 
| 108 | 
            +
                buf << "%3d° " % (hsl[0] % 360)
         | 
| 109 | 
            +
                buf << "%3d%% " % (hsl[1]*100+0.5).to_i
         | 
| 110 | 
            +
                buf << "%3d%%)" % (hsl[2]*100+0.5).to_i
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                if alpha != 255
         | 
| 113 | 
            +
                  buf << " - α(%3d%%)" % (alpha*100/255+0.5).to_i
         | 
| 114 | 
            +
                else
         | 
| 115 | 
            +
                  buf << "          "  ## add empty for 255 (full opacity)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                ## note: add alpha channel to hex
         | 
| 119 | 
            +
                alpha_hex = '%02x' % alpha
         | 
| 120 | 
            +
                name = NAMES[ hex+alpha_hex ]
         | 
| 121 | 
            +
                buf << " - #{name}"  if name
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                buf
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
              class << self
         | 
| 126 | 
            +
                alias_method :fmt, :format
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            end  # class Color
         | 
| 130 | 
            +
            end  # module Pixelart
         | 
| 131 | 
            +
             | 
| 132 | 
            +
             | 
| @@ -0,0 +1,106 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            ## inspired / helped by
         | 
| 3 | 
            +
            ##  https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
         | 
| 4 | 
            +
            ##  https://github.com/mistic100/tinygradient
         | 
| 5 | 
            +
            ##   https://mistic100.github.io/tinygradient/
         | 
| 6 | 
            +
            ##   https://bsouthga.dev/posts/color-gradients-with-python
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Pixelart
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            class Gradient
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def initialize( *stops )
         | 
| 14 | 
            +
                ## note:  convert stop colors to rgb triplets e.g.
         | 
| 15 | 
            +
                ##         from #ffffff to [255,255,255]
         | 
| 16 | 
            +
                ##              #000000 to [0,0,0]  etc.
         | 
| 17 | 
            +
                @stops = stops.map do |stop|
         | 
| 18 | 
            +
                           stop = Color.parse( stop )
         | 
| 19 | 
            +
                           [Color.r(stop), Color.g(stop), Color.b(stop)]
         | 
| 20 | 
            +
                         end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
             | 
| 24 | 
            +
              def colors( steps )
         | 
| 25 | 
            +
                segments = @stops.size - 1
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                ## note: gradient will include start (first)
         | 
| 28 | 
            +
                ##   AND stop color (last) - stop color is NOT excluded for now!!
         | 
| 29 | 
            +
                if segments == 1
         | 
| 30 | 
            +
                   start = @stops[0]
         | 
| 31 | 
            +
                   stop  = @stops[1]
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                   gradient = linear_gradient( start, stop, steps,
         | 
| 34 | 
            +
                                               include_stop: true )
         | 
| 35 | 
            +
                else
         | 
| 36 | 
            +
                  steps_per_segment, mod = steps.divmod( segments )
         | 
| 37 | 
            +
                  raise ArgumentError, "steps (#{steps}) must be divisible by # of segments (#{segments}); expected mod of 0 but got #{mod}"   if mod != 0
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  gradient = []
         | 
| 40 | 
            +
                  segments.times do |segment|
         | 
| 41 | 
            +
                    start = @stops[segment]
         | 
| 42 | 
            +
                    stop  = @stops[segment+1]
         | 
| 43 | 
            +
                    include_stop =  (segment == segments-1)  ## note: only include stop if last segment!!
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    # print "  segment #{segment+1}/#{segments} #{steps_per_segment} color(s) - "
         | 
| 46 | 
            +
                    # print "  start: #{start.inspect}  "
         | 
| 47 | 
            +
                    # print  include_stop ? 'include' : 'exclude'
         | 
| 48 | 
            +
                    # print " stop:  #{stop.inspect}"
         | 
| 49 | 
            +
                    # print "\n"
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    gradient += linear_gradient( start, stop, steps_per_segment,
         | 
| 52 | 
            +
                                                 include_stop: include_stop )
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                ## convert to color (Integer)
         | 
| 57 | 
            +
                gradient.map do |color|
         | 
| 58 | 
            +
                  Color.rgb( *color )
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
             | 
| 63 | 
            +
             | 
| 64 | 
            +
             def interpolate( start, stop, steps, n )
         | 
| 65 | 
            +
               ## note: n - expected to start with 1,2,3,etc.
         | 
| 66 | 
            +
               color = []
         | 
| 67 | 
            +
               3.times do |i|
         | 
| 68 | 
            +
                 stepize = Float(stop[i] - start[i]) / Float(steps-1)
         | 
| 69 | 
            +
                 value = stepize * n
         | 
| 70 | 
            +
                 ## convert back to Integer from Float
         | 
| 71 | 
            +
                 ##   add 0.5 for rounding up (starting with 0.5) - why? why not?
         | 
| 72 | 
            +
                 value = (value+0.5).to_i
         | 
| 73 | 
            +
                 value = start[i] + value
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                 color << value
         | 
| 76 | 
            +
               end
         | 
| 77 | 
            +
               color
         | 
| 78 | 
            +
             end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
             | 
| 81 | 
            +
             def linear_gradient( start, stop, steps,
         | 
| 82 | 
            +
                                  include_stop: true )
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              gradient = [start]  ## auto-add start color (first color in gradient)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              if include_stop
         | 
| 87 | 
            +
                1.upto( steps-2 ).each do |n|  ## sub two (-2), that is, start and stop color
         | 
| 88 | 
            +
                  gradient << interpolate( start, stop, steps, n )
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
                # note: use original passed in stop color (should match calculated)
         | 
| 91 | 
            +
                gradient << stop
         | 
| 92 | 
            +
              else
         | 
| 93 | 
            +
                1.upto( steps-1 ).each do |n|  ## sub one (-1), that is, start color only
         | 
| 94 | 
            +
                  ## note: add one (+1) to steps because stop color gets excluded (not included)!!
         | 
| 95 | 
            +
                  gradient << interpolate( start, stop, steps+1, n )
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              gradient
         | 
| 100 | 
            +
            end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
             | 
| 103 | 
            +
             | 
| 104 | 
            +
            end  # class Gradient
         | 
| 105 | 
            +
            end  # module Pixelart
         | 
| 106 | 
            +
             | 
    
        data/lib/pixelart/image.rb
    CHANGED
    
    | @@ -1,57 +1,5 @@ | |
| 1 1 | 
             
            module Pixelart
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
            class Color
         | 
| 7 | 
            -
              def self.parse( color )
         | 
| 8 | 
            -
                if color.is_a?( Integer )  ## e.g. assumes ChunkyPNG::Color.rgb() or such
         | 
| 9 | 
            -
                  color ## pass through as is 1:1
         | 
| 10 | 
            -
                elsif color.is_a?( Array )  ## assume array of hsl(a) e. g. [180, 0.86, 0.88]
         | 
| 11 | 
            -
                  ChunkyPNG::Color.from_hsl( *color )
         | 
| 12 | 
            -
                elsif color.is_a?( String )
         | 
| 13 | 
            -
                  if color.downcase == 'transparent'   ## special case for builtin colors
         | 
| 14 | 
            -
                    ChunkyPNG::Color::TRANSPARENT
         | 
| 15 | 
            -
                  else
         | 
| 16 | 
            -
                    ## note: return an Integer !!! (not a Color class or such!!! )
         | 
| 17 | 
            -
                    ChunkyPNG::Color.from_hex( color )
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
                else
         | 
| 20 | 
            -
                  raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
              end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
              def self.from_hex( hex )
         | 
| 25 | 
            -
                ## Creates a color by converting it from a string in hex notation.
         | 
| 26 | 
            -
                ##
         | 
| 27 | 
            -
                ## It supports colors with (#rrggbbaa) or without (#rrggbb)
         | 
| 28 | 
            -
                ##  alpha channel as well as the 3-digit short format (#rgb)
         | 
| 29 | 
            -
                ## for those without. Color strings may include
         | 
| 30 | 
            -
                ## the prefix "0x" or "#"".
         | 
| 31 | 
            -
                ChunkyPNG::Color.from_hex( hex )
         | 
| 32 | 
            -
              end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
              def self.from_hsl( hue, saturation, lightness, alpha=255)
         | 
| 35 | 
            -
                ChunkyPNG::Color.from_hsl( hue,
         | 
| 36 | 
            -
                                           saturation,
         | 
| 37 | 
            -
                                           lightness,
         | 
| 38 | 
            -
                                           alpha )
         | 
| 39 | 
            -
              end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
              def self.to_hex( color, include_alpha: true )
         | 
| 43 | 
            -
                ChunkyPNG::Color.to_hex( color, include_alpha )
         | 
| 44 | 
            -
              end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
              def self.to_hsl( color, include_alpha: true )
         | 
| 47 | 
            -
                # Returns an array with the separate HSL components of a color.
         | 
| 48 | 
            -
                ChunkyPNG::Color.to_hsl( color, include_alpha )
         | 
| 49 | 
            -
              end
         | 
| 50 | 
            -
            end  # class Color
         | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 3 | 
             
            class Image
         | 
| 56 4 |  | 
| 57 5 | 
             
            def self.read( path )   ## convenience helper
         | 
| @@ -82,7 +30,7 @@ end | |
| 82 30 |  | 
| 83 31 |  | 
| 84 32 |  | 
| 85 | 
            -
            def initialize( width, height, initial= | 
| 33 | 
            +
            def initialize( width, height, initial=Color::TRANSPARENT )
         | 
| 86 34 |  | 
| 87 35 | 
             
              if initial.is_a?( ChunkyPNG::Image )
         | 
| 88 36 | 
             
                @img = initial
         | 
| @@ -117,19 +65,71 @@ alias_method :scale, :zoom | |
| 117 65 |  | 
| 118 66 |  | 
| 119 67 |  | 
| 68 | 
            +
            #######################
         | 
| 69 | 
            +
            ## filter / effects
         | 
| 120 70 |  | 
| 121 | 
            -
            def  | 
| 122 | 
            -
               | 
| 123 | 
            -
             | 
| 124 | 
            -
              end.to_h
         | 
| 71 | 
            +
            def grayscale
         | 
| 72 | 
            +
              img = @img.grayscale
         | 
| 73 | 
            +
              Image.new( img.width, img.height, img )
         | 
| 125 74 | 
             
            end
         | 
| 126 75 |  | 
| 127 76 | 
             
            ## add replace_colors alias too? - why? why not?
         | 
| 128 77 | 
             
            def change_colors( color_map )
         | 
| 78 | 
            +
              color_map = _parse_color_map( color_map )
         | 
| 79 | 
            +
             | 
| 129 80 | 
             
              img = @img.dup  ## note: make a deep copy!!!
         | 
| 130 | 
            -
               | 
| 131 | 
            -
              ## pp color_map
         | 
| 81 | 
            +
              _change_colors!( img, color_map )
         | 
| 132 82 |  | 
| 83 | 
            +
              ## wrap into Pixelart::Image - lets you use zoom() and such
         | 
| 84 | 
            +
              Image.new( img.width, img.height, img )
         | 
| 85 | 
            +
            end
         | 
| 86 | 
            +
            alias_method :recolor, :change_colors
         | 
| 87 | 
            +
             | 
| 88 | 
            +
             | 
| 89 | 
            +
             | 
| 90 | 
            +
            ## predefined palette8bit color maps
         | 
| 91 | 
            +
            ##     (grayscale to sepia/blue/false/etc.)
         | 
| 92 | 
            +
            ##  - todo/check - keep "shortcut" convenience predefined map - why? why not?
         | 
| 93 | 
            +
            PALETTE8BIT = {
         | 
| 94 | 
            +
              sepia: Palette8bit::GRAYSCALE.zip( Palette8bit::SEPIA ).to_h,
         | 
| 95 | 
            +
              blue:  Palette8bit::GRAYSCALE.zip( Palette8bit::BLUE ).to_h,
         | 
| 96 | 
            +
              false: Palette8bit::GRAYSCALE.zip( Palette8bit::FALSE ).to_h,
         | 
| 97 | 
            +
            }
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            def change_palette8bit( palette )
         | 
| 100 | 
            +
              ## step 0: mapping from grayscale to new 8bit palette (256 colors)
         | 
| 101 | 
            +
              color_map = if palette.is_a?( String ) || palette.is_a?( Symbol )
         | 
| 102 | 
            +
                             PALETTE8BIT[ palette.to_sym ]
         | 
| 103 | 
            +
                             ## todo/fix: check for missing/undefined palette not found - why? why not?
         | 
| 104 | 
            +
                          else
         | 
| 105 | 
            +
                             ##  make sure we have colors all in Integer not names, hex, etc.
         | 
| 106 | 
            +
                             palette = _parse_colors( palette )
         | 
| 107 | 
            +
                             Palette8bit::GRAYSCALE.zip( palette ).to_h
         | 
| 108 | 
            +
                          end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
              ## step 1: convert to grayscale (256 colors)
         | 
| 111 | 
            +
              img = @img.grayscale
         | 
| 112 | 
            +
              _change_colors!( img, color_map )
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              ## wrap into Pixelart::Image - lets you use zoom() and such
         | 
| 115 | 
            +
              Image.new( img.width, img.height, img )
         | 
| 116 | 
            +
            end
         | 
| 117 | 
            +
            alias_method :change_palette256, :change_palette8bit
         | 
| 118 | 
            +
             | 
| 119 | 
            +
             | 
| 120 | 
            +
            ####
         | 
| 121 | 
            +
            ##  private helpers
         | 
| 122 | 
            +
            def _parse_colors( colors )
         | 
| 123 | 
            +
              colors.map {|color| Color.parse( color ) }
         | 
| 124 | 
            +
            end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            def _parse_color_map( color_map )
         | 
| 127 | 
            +
              color_map.map do |k,v|
         | 
| 128 | 
            +
                [Color.parse(k),  Color.parse(v)]
         | 
| 129 | 
            +
              end.to_h
         | 
| 130 | 
            +
            end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            def _change_colors!( img, color_map )
         | 
| 133 133 | 
             
              img.width.times do |x|
         | 
| 134 134 | 
             
                img.height.times do |y|
         | 
| 135 135 | 
             
                  color = img[x,y]
         | 
| @@ -137,12 +137,7 @@ def change_colors( color_map ) | |
| 137 137 | 
             
                  img[x,y] = new_color  if new_color
         | 
| 138 138 | 
             
                end
         | 
| 139 139 | 
             
              end
         | 
| 140 | 
            -
             | 
| 141 | 
            -
              ## wrap into Pixelart::Image - lets you use zoom() and such
         | 
| 142 | 
            -
              Image.new( img.width, img.height, img )
         | 
| 143 140 | 
             
            end
         | 
| 144 | 
            -
            alias_method :recolor, :change_colors
         | 
| 145 | 
            -
             | 
| 146 141 |  | 
| 147 142 |  | 
| 148 143 |  | 
| @@ -176,6 +171,7 @@ def []=( x, y, value )  @img[x,y]=value; end | |
| 176 171 | 
             
            def pixels()       @img.pixels; end
         | 
| 177 172 |  | 
| 178 173 | 
             
            ## return image ref - use a different name - why? why not?
         | 
| 174 | 
            +
            ##   change to to_image  - why? why not?
         | 
| 179 175 | 
             
            def image()        @img; end
         | 
| 180 176 |  | 
| 181 177 |  | 
| @@ -204,7 +200,7 @@ end | |
| 204 200 | 
             
            def self.parse_colors( colors )
         | 
| 205 201 | 
             
              if colors.is_a?( Array )   ## convenience shortcut
         | 
| 206 202 | 
             
                ## note: always auto-add color 0 as pre-defined transparent - why? why not?
         | 
| 207 | 
            -
                h = { '0' =>  | 
| 203 | 
            +
                h = { '0' => Color::TRANSPARENT }
         | 
| 208 204 | 
             
                colors.each_with_index do |color, i|
         | 
| 209 205 | 
             
                   h[ (i+1).to_s ] = Color.parse( color )
         | 
| 210 206 | 
             
                end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module Pixelart
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class ImagePalette8bit < Image  # or use Palette256 alias?
         | 
| 5 | 
            +
              def initialize( colors, size: 1, spacing: nil )
         | 
| 6 | 
            +
                ## todo/check: change size arg to pixel or such? better name/less confusing - why? why not?
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                ## todo/check: assert colors MUST have 256 colors!!!!
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                ## use a "smart" default if no spacing set
         | 
| 11 | 
            +
                ##   0 for for (pixel) size == 1
         | 
| 12 | 
            +
                ##   1 for the rest
         | 
| 13 | 
            +
                spacing = size == 1 ? 0 : 1  if spacing.nil?
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                img = ChunkyPNG::Image.new( 32*size+(32-1)*spacing,
         | 
| 16 | 
            +
                                             8*size+(8-1)*spacing )
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                colors.each_with_index do |color,i|
         | 
| 19 | 
            +
                  y,x = i.divmod( 32 )
         | 
| 20 | 
            +
                  if size > 1
         | 
| 21 | 
            +
                    size.times do |n|
         | 
| 22 | 
            +
                      size.times do |m|
         | 
| 23 | 
            +
                        img[ x*size+n+spacing*x,
         | 
| 24 | 
            +
                             y*size+m+spacing*y] = color
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  else
         | 
| 28 | 
            +
                    img[x,y] = color
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                super( img.width, img.height, img )
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end # class ImagePalette8bit
         | 
| 35 | 
            +
            end # module Pixelart
         | 
| 36 | 
            +
             | 
| 37 | 
            +
             | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            module Pixelart
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Palette8bit     # or use Palette256 alias?
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
              ## auto-add grayscale 0 to 255
         | 
| 8 | 
            +
              ##  e.g. rgb(0,0,0)
         | 
| 9 | 
            +
              ##       rgb(1,1,1)
         | 
| 10 | 
            +
              ##       rgb(2,2,2)
         | 
| 11 | 
            +
              ##       ...
         | 
| 12 | 
            +
              ##       rgb(255,255,255)
         | 
| 13 | 
            +
              GRAYSCALE = (0..255).map { |n| Color.rgb( n, n, n ) }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
             | 
| 16 | 
            +
               ## 8x32 gradient color stops
         | 
| 17 | 
            +
               ##   see https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              SEPIA_STOPS = [
         | 
| 20 | 
            +
                ['080400', '262117'],
         | 
| 21 | 
            +
                ['272218', '453E2F'],
         | 
| 22 | 
            +
                ['463F30', '645C48'],
         | 
| 23 | 
            +
                ['655D48', '837A60'],
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                ['847A60', 'A29778'],
         | 
| 26 | 
            +
                ['A39878', 'C1B590'],
         | 
| 27 | 
            +
                ['C2B691', 'E0D2A8'],
         | 
| 28 | 
            +
                ['E1D3A9', 'FEEFBF'],
         | 
| 29 | 
            +
              ]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              BLUE_STOPS = [
         | 
| 32 | 
            +
                ['000000', '001F3E'],
         | 
| 33 | 
            +
                ['002040', '003F7E'],
         | 
| 34 | 
            +
                ['004080', '005FBD'],
         | 
| 35 | 
            +
                ['0060BF', '007FFD'],
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                ['0080FF', '009FFF'],
         | 
| 38 | 
            +
                ['00A0FF', '00BFFF'],
         | 
| 39 | 
            +
                ['00C0FF', '00DFFF'],
         | 
| 40 | 
            +
                ['00E0FF', '00FEFF'],
         | 
| 41 | 
            +
              ]
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              FALSE_STOPS = [
         | 
| 44 | 
            +
                ['FF00FF', '6400FF'],
         | 
| 45 | 
            +
                ['5F00FF', '003CFF'],
         | 
| 46 | 
            +
                ['0041FF', '00DCFF'],
         | 
| 47 | 
            +
                ['00E1FF', '00FF82'],
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                ['00FF7D', '1EFF00'],
         | 
| 50 | 
            +
                ['23FF00', 'BEFF00'],
         | 
| 51 | 
            +
                ['C3FF00', 'FFA000'],
         | 
| 52 | 
            +
                ['FF9B00', 'FF0000'],
         | 
| 53 | 
            +
              ]
         | 
| 54 | 
            +
             | 
| 55 | 
            +
             | 
| 56 | 
            +
              def self.build_palette( gradients )
         | 
| 57 | 
            +
                colors_per_gradient, mod = 256.divmod( gradients.size )
         | 
| 58 | 
            +
                raise ArgumentError, "8bit palette - 256 must be divisible by # of gradients (#{gradients.size}; expected mod of 0 but got #{mod}"   if mod != 0
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                colors = []
         | 
| 61 | 
            +
                gradients.each do |stops|
         | 
| 62 | 
            +
                  colors += Gradient.new( *stops ).colors( colors_per_gradient )
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
                colors
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              SEPIA     = build_palette( SEPIA_STOPS )
         | 
| 68 | 
            +
              BLUE      = build_palette( BLUE_STOPS )
         | 
| 69 | 
            +
              FALSE     = build_palette( FALSE_STOPS )
         | 
| 70 | 
            +
            end  # class Palette8bit
         | 
| 71 | 
            +
            end # module Pixelart
         | 
| 72 | 
            +
             | 
| @@ -0,0 +1,117 @@ | |
| 1 | 
            +
            module Pixelart
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Pixelator    # or use Minifier or such - rename - why? why not?
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize( img, width=24, height=24 )
         | 
| 7 | 
            +
                @img    = img.is_a?( Image ) ? img.image : img  ## "unwrap" if Pixelart::Image
         | 
| 8 | 
            +
                @width  = width
         | 
| 9 | 
            +
                @height = height
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                ## calculate pixel size / density / resolution
         | 
| 12 | 
            +
                ##   how many pixels per pixel?
         | 
| 13 | 
            +
                @xsize, @xoverflow = img.width.divmod( width )
         | 
| 14 | 
            +
                @ysize, @yoverflow = img.height.divmod( height )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                puts "minify image size from (#{@img.width}x#{@img.height}) to (#{width}x#{height})"
         | 
| 17 | 
            +
                puts "  pixel size (#{@xsize}x#{@ysize}) - #{@xsize*@ysize} pixel(s) per pixel"
         | 
| 18 | 
            +
                puts "    overflow x: #{@xoverflow}, y: #{@yoverflow} pixel(s)"    if @xoverflow > 0 || @yoverflow > 0
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
              def grid( spacing: 10 )
         | 
| 23 | 
            +
                width  = @img.width  + (@width-1)*spacing
         | 
| 24 | 
            +
                height = @img.height + (@height-1)*spacing
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                img = ChunkyPNG::Image.new( width, height, ChunkyPNG::Color::WHITE )
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                @img.width.times do |x|
         | 
| 29 | 
            +
                  xpixel = x/@xsize
         | 
| 30 | 
            +
                  @img.height.times do |y|
         | 
| 31 | 
            +
                    ypixel = y/@ysize
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    ## clip overflow pixels
         | 
| 34 | 
            +
                    xpixel = @width-1   if xpixel >= @width
         | 
| 35 | 
            +
                    ypixel = @height-1  if ypixel >= @height
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    color = @img[x,y]
         | 
| 38 | 
            +
                    img[x + spacing*xpixel,
         | 
| 39 | 
            +
                        y + spacing*ypixel] = color
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                Image.new( img.width, img.height, img )  ## wrap in Pixelart::Image - why? why not?
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
             | 
| 47 | 
            +
              # pixels by coordinates (x/y) with color statistics / usage
         | 
| 48 | 
            +
              def pixels
         | 
| 49 | 
            +
                @pixels ||= begin
         | 
| 50 | 
            +
                              pixels = []
         | 
| 51 | 
            +
                              @img.width.times do |x|
         | 
| 52 | 
            +
                                xpixel = x/@xsize
         | 
| 53 | 
            +
                                @img.height.times do |y|
         | 
| 54 | 
            +
                                  ypixel = y/@ysize
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                                  ## skip/cut off overflow pixels
         | 
| 57 | 
            +
                                  next if xpixel >= @width || ypixel >= @height
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                                  color = @img[x,y]
         | 
| 60 | 
            +
                                  colors = pixels[xpixel+ypixel*@width] ||= Hash.new(0)
         | 
| 61 | 
            +
                                  colors[ color ] += 1
         | 
| 62 | 
            +
                                end
         | 
| 63 | 
            +
                              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                              ## sort pixel colors by usage / count (highest first)
         | 
| 66 | 
            +
                              pixels = pixels.map do |pixel|
         | 
| 67 | 
            +
                                                     pixel.sort do |l,r|
         | 
| 68 | 
            +
                                                                  r[1] <=> l[1]
         | 
| 69 | 
            +
                                                                end.to_h
         | 
| 70 | 
            +
                                                  end
         | 
| 71 | 
            +
                              pixels
         | 
| 72 | 
            +
                            end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              def pixel(x,y)  pixels[x+y*@width]; end
         | 
| 76 | 
            +
              alias_method :[], :pixel
         | 
| 77 | 
            +
             | 
| 78 | 
            +
             | 
| 79 | 
            +
              def can_pixelate?
         | 
| 80 | 
            +
                # check if any pixel has NOT a color with a 50% majority?
         | 
| 81 | 
            +
                count = 0
         | 
| 82 | 
            +
                @width.times do |x|
         | 
| 83 | 
            +
                  @height.times do |y|
         | 
| 84 | 
            +
                    pixel = pixel( x, y )
         | 
| 85 | 
            +
                    sum         = pixel.values.sum
         | 
| 86 | 
            +
                    color_count = pixel.values[0]
         | 
| 87 | 
            +
                    if color_count < (sum/2)
         | 
| 88 | 
            +
                      count += 1
         | 
| 89 | 
            +
                      ## todo/check: stor warn in a public errors or warns array - why? why not?
         | 
| 90 | 
            +
                      puts "!! WARN #{count} - pixel (#{x}/#{y}) - no majority (50%) color:"
         | 
| 91 | 
            +
                      pp pixel
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                count == 0    ## return true if not warnings found
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
              alias_method :pixelate?, :can_pixelate?
         | 
| 99 | 
            +
             | 
| 100 | 
            +
             | 
| 101 | 
            +
              def pixelate
         | 
| 102 | 
            +
                img = ChunkyPNG::Image.new( @width, @height )
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                @width.times do |x|
         | 
| 105 | 
            +
                  @height.times do |y|
         | 
| 106 | 
            +
                    pixel = pixel( x, y )
         | 
| 107 | 
            +
                    color = pixel.keys[0]
         | 
| 108 | 
            +
                    img[x,y] = color
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                Image.new( img.width, img.height, img )  ## wrap in Pixelart::Image - why? why not?
         | 
| 113 | 
            +
              end
         | 
| 114 | 
            +
            end  # class Pixelator
         | 
| 115 | 
            +
            end  # module Pixelart
         | 
| 116 | 
            +
             | 
| 117 | 
            +
             | 
    
        data/lib/pixelart/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: pixelart
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.7
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Gerald Bauer
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021-04- | 
| 11 | 
            +
            date: 2021-04-23 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: chunky_png
         | 
| @@ -73,9 +73,15 @@ files: | |
| 73 73 | 
             
            - README.md
         | 
| 74 74 | 
             
            - Rakefile
         | 
| 75 75 | 
             
            - lib/pixelart.rb
         | 
| 76 | 
            +
            - lib/pixelart/base.rb
         | 
| 77 | 
            +
            - lib/pixelart/color.rb
         | 
| 78 | 
            +
            - lib/pixelart/gradient.rb
         | 
| 76 79 | 
             
            - lib/pixelart/image.rb
         | 
| 80 | 
            +
            - lib/pixelart/misc.rb
         | 
| 81 | 
            +
            - lib/pixelart/palette.rb
         | 
| 82 | 
            +
            - lib/pixelart/pixelator.rb
         | 
| 77 83 | 
             
            - lib/pixelart/version.rb
         | 
| 78 | 
            -
            homepage: https://github.com/ | 
| 84 | 
            +
            homepage: https://github.com/rubycoco/pixel
         | 
| 79 85 | 
             
            licenses:
         | 
| 80 86 | 
             
            - Public Domain
         | 
| 81 87 | 
             
            metadata: {}
         |