Class: Pixelart::Image

Inherits:
Object
  • Object
show all
Defined in:
lib/pixelart/led.rb,
lib/pixelart/image.rb

Direct Known Subclasses

ImageColorBar, ImagePalette8bit

Constant Summary collapse

CHARS =

todo/check: rename to default chars or such? why? why not?

'.@xo^~%*+=:'
PALETTE8BIT =

predefined palette8bit color maps

   (grayscale to sepia/blue/false/etc.)
- todo/check - keep "shortcut" convenience predefined map - why? why not?
{
  sepia: Palette8bit::GRAYSCALE.zip( Palette8bit::SEPIA ).to_h,
  blue:  Palette8bit::GRAYSCALE.zip( Palette8bit::BLUE ).to_h,
  false: Palette8bit::GRAYSCALE.zip( Palette8bit::FALSE ).to_h,
}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(width, height, initial = Color::TRANSPARENT) ⇒ Image

Returns a new instance of Image.



52
53
54
55
56
57
58
59
60
# File 'lib/pixelart/image.rb', line 52

def initialize( width, height, initial=Color::TRANSPARENT )

  if initial.is_a?( ChunkyPNG::Image )
    @img = initial
  else
    ## todo/check - initial - use parse_color here too e.g. allow "#fff" too etc.

    @img = ChunkyPNG::Image.new( width, height, initial )
  end
end

Class Method Details

.parse(pixels, colors:, chars: CHARS) ⇒ Object

todo/check: support default chars encoding auto-of-the-box always

or require user-defined chars to be passed in - why? why not?


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/pixelart/image.rb', line 17

def self.parse( pixels, colors:, chars: CHARS )
  has_keys  = colors.is_a?(Hash)   ## check if passed-in user-defined keys (via hash table)?


  colors = parse_colors( colors )
  pixels = parse_pixels( pixels )

  width  = pixels.reduce(1) {|width,row| row.size > width ? row.size : width }
  height = pixels.size

  img = new( width, height )

  pixels.each_with_index do |row,y|
    row.each_with_index do |color,x|
      pixel = if has_keys     ## if passed-in user-defined keys check only the user-defined keys

                colors[color]
              else
                ## try map ascii art char (.@xo etc.) to color index (0,1,2)

                ##   if no match found - fallback on assuming draw by number (0 1 2 etc.) encoding

                pos = chars.index( color )
                if pos
                  colors[ pos.to_s ]
                else ## assume nil (not found)

                  colors[ color ]
                end
              end

      img[x,y] = pixel
    end # each row

  end # each data


  img
end

.parse_colors(colors) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/pixelart/image.rb', line 220

def self.parse_colors( colors )
  if colors.is_a?( Array )   ## convenience shortcut

    ## note: always auto-add color 0 as pre-defined transparent - why? why not?

    h = { '0' => Color::TRANSPARENT }
    colors.each_with_index do |color, i|
      h[ (i+1).to_s ] = Color.parse( color )
    end
    h
  else  ## assume hash table with color map

    ## convert into ChunkyPNG::Color

    colors.map do |key,color|
      ## always convert key to string why? why not?  use symbol?

      [ key.to_s, Color.parse( color ) ]
    end.to_h
  end
end

.parse_pixels(pixels) ⇒ Object

helpers



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/pixelart/image.rb', line 204

def self.parse_pixels( pixels )
  data = []
  pixels.each_line do |line|
    line = line.strip
    next if line.start_with?( '#' ) || line.empty?   ## note: allow comments and empty lines


    ## note: allow multiple spaces or tabs to separate pixel codes

    ##  e.g.   o o o o o o o o o o o o dg lg w w lg w lg lg dg dg w w  lg dg o o o o o o o o o o o

    ##    or

    data << line.split( /[ \t]+/)
 end
 data
end

.read(path) ⇒ Object

convenience helper



5
6
7
8
9
# File 'lib/pixelart/image.rb', line 5

def self.read( path )   ## convenience helper

  img_inner = ChunkyPNG::Image.from_file( path )
  img = new( img_inner.width, img_inner.height, img_inner )
  img
end

Instance Method Details

#[](x, y) ⇒ Object



187
# File 'lib/pixelart/image.rb', line 187

def []( x, y )          @img[x,y]; end

#[]=(x, y, value) ⇒ Object



188
# File 'lib/pixelart/image.rb', line 188

def []=( x, y, value )  @img[x,y]=value; end

#_change_colors!(img, color_map) ⇒ Object



151
152
153
154
155
156
157
158
159
# File 'lib/pixelart/image.rb', line 151

def _change_colors!( img, color_map )
  img.width.times do |x|
    img.height.times do |y|
      color = img[x,y]
      new_color = color_map[color]
      img[x,y] = new_color  if new_color
    end
  end
end

#_parse_color_map(color_map) ⇒ Object



145
146
147
148
149
# File 'lib/pixelart/image.rb', line 145

def _parse_color_map( color_map )
  color_map.map do |k,v|
    [Color.parse(k),  Color.parse(v)]
  end.to_h
end

#_parse_colors(colors) ⇒ Object

private helpers



141
142
143
# File 'lib/pixelart/image.rb', line 141

def _parse_colors( colors )
  colors.map {|color| Color.parse( color ) }
end

#change_colors(color_map) ⇒ Object Also known as: recolor

add replace_colors alias too? - why? why not?



96
97
98
99
100
101
102
103
104
# File 'lib/pixelart/image.rb', line 96

def change_colors( color_map )
  color_map = _parse_color_map( color_map )

  img = @img.dup  ## note: make a deep copy!!!

  _change_colors!( img, color_map )

  ## wrap into Pixelart::Image - lets you use zoom() and such

  Image.new( img.width, img.height, img )
end

#change_palette8bit(palette) ⇒ Object Also known as: change_palette256



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/pixelart/image.rb', line 118

def change_palette8bit( palette )
  ## step 0: mapping from grayscale to new 8bit palette (256 colors)

  color_map = if palette.is_a?( String ) || palette.is_a?( Symbol )
                 PALETTE8BIT[ palette.to_sym ]
                 ## todo/fix: check for missing/undefined palette not found - why? why not?

              else
                 ##  make sure we have colors all in Integer not names, hex, etc.

                 palette = _parse_colors( palette )
                 Palette8bit::GRAYSCALE.zip( palette ).to_h
              end

  ## step 1: convert to grayscale (256 colors)

  img = @img.grayscale
  _change_colors!( img, color_map )

  ## wrap into Pixelart::Image - lets you use zoom() and such

  Image.new( img.width, img.height, img )
end

#compose!(other, x = 0, y = 0) ⇒ Object Also known as: paste!



178
179
180
# File 'lib/pixelart/image.rb', line 178

def compose!( other, x=0, y=0 )
  @img.compose!( other.image, x, y )    ## note: "unwrap" inner image ref

end

#grayscaleObject

filter / effects



90
91
92
93
# File 'lib/pixelart/image.rb', line 90

def grayscale
  img = @img.grayscale
  Image.new( img.width, img.height, img )
end

#heightObject



185
# File 'lib/pixelart/image.rb', line 185

def height()       @img.height; end

#imageObject

return image ref - use a different name - why? why not?

change to to_image  - why? why not?


197
# File 'lib/pixelart/image.rb', line 197

def image()        @img; end

#led(led = 8, spacing: 2, round_corner: false) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/pixelart/led.rb', line 5

def led( led=8, spacing: 2, round_corner: false )

  width  = @img.width*led  + (@img.width-1)*spacing
  height = @img.height*led + (@img.height-1)*spacing

  puts "  #{width}x#{height}"

  img = Image.new( width, height, Color::BLACK )

  @img.width.times do |x|
    @img.height.times do |y|
      pixel = @img[x,y]
      pixel = Color::BLACK  if pixel == Color::TRANSPARENT
      led.times do |n|
        led.times do |m|
          ## round a little - drop all four corners for now

          next  if round_corner &&
                  [[0,0],[0,1],[1,0],[1,1],[0,2],[2,0],
                   [0,led-1],[0,led-2],[1,led-1],[1,led-2],[0,led-3],[2,led-1],
                   [led-1,0],[led-1,1],[led-2,0],[led-2,1],[led-1,2],[led-3,0],
                   [led-1,led-1],[led-1,led-2],[led-2,led-1],[led-2,led-2],[led-1,led-3],[led-3,led-1],
                  ].include?( [n,m] )
          img[x*led+n + spacing*x,
              y*led+m + spacing*y] = pixel
        end
      end
    end
  end
  img
end

#pixelsObject



190
# File 'lib/pixelart/image.rb', line 190

def pixels()       @img.pixels; end

#save(path, constraints = {}) ⇒ Object Also known as: write

(image) delegates

todo/check: add some more??


167
168
169
170
171
172
173
174
# File 'lib/pixelart/image.rb', line 167

def save( path, constraints = {} )
  # step 1: make sure outdir exits

  outdir = File.dirname( path )
  FileUtils.mkdir_p( outdir )  unless Dir.exist?( outdir )

  # step 2: save

  @img.save( path, constraints )
end

#widthObject



184
# File 'lib/pixelart/image.rb', line 184

def width()        @img.width; end

#zoom(zoom = 2) ⇒ Object Also known as: scale



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/pixelart/image.rb', line 64

def zoom( zoom=2 )
  ## create a new zoom factor x image (2x, 3x, etc.)


  img = Image.new( @img.width*zoom,
                   @img.height*zoom )

  @img.height.times do |y|
    @img.width.times do |x|
      pixel = @img[x,y]
      zoom.times do |n|
        zoom.times do |m|
          img[n+zoom*x,m+zoom*y] = pixel
        end
      end
    end # each x

  end # each y


  img
end