Class: Pixelart::Image
- Inherits:
-
Object
- Object
- Pixelart::Image
- Defined in:
- lib/pixelart/led.rb,
lib/pixelart/blur.rb,
lib/pixelart/image.rb,
lib/pixelart/spots.rb,
lib/pixelart/sketch.rb
Direct Known Subclasses
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
-
.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?.
- .parse_colors(colors) ⇒ Object
-
.parse_pixels(pixels) ⇒ Object
helpers.
-
.read(path) ⇒ Object
convenience helper.
Instance Method Summary collapse
- #[](x, y) ⇒ Object
- #[]=(x, y, value) ⇒ Object
- #_change_colors!(img, color_map) ⇒ Object
- #_parse_color_map(color_map) ⇒ Object
-
#_parse_colors(colors) ⇒ Object
private helpers.
- #blur(blur = 2) ⇒ Object
-
#change_colors(color_map) ⇒ Object
(also: #recolor)
add replace_colors alias too? - why? why not?.
- #change_palette8bit(palette) ⇒ Object (also: #change_palette256)
- #compose!(other, x = 0, y = 0) ⇒ Object (also: #paste!)
- #flip ⇒ Object (also: #flip_horizontally)
-
#grayscale ⇒ Object
(also: #greyscale)
filter / effects.
- #height ⇒ Object
-
#image ⇒ Object
return image ref - use a different name - why? why not? change to to_image - why? why not?.
-
#initialize(width, height, initial = Color::TRANSPARENT) ⇒ Image
constructor
A new instance of Image.
- #led(led = 8, spacing: 2, round_corner: false) ⇒ Object
- #mirror ⇒ Object (also: #flip_vertically, #flop)
- #pixels ⇒ Object
-
#save(path, constraints = {}) ⇒ Object
(also: #write)
(image) delegates todo/check: add some more??.
- #sketch(sketch = 4, line: 1) ⇒ Object
- #spots(spot = 10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false) ⇒ Object
- #spots_hidef(spot = 10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false) ⇒ Object (also: #spots_hd)
- #width ⇒ Object
- #zoom(zoom = 2) ⇒ Object (also: #scale)
Constructor Details
#initialize(width, height, initial = Color::TRANSPARENT) ⇒ Image
Returns a new instance of Image.
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/pixelart/image.rb', line 52 def initialize( width, height, initial=Color::TRANSPARENT ) ### todo/fix: ## change params to *args only - why? why not? ## make width/height optional if image passed in? 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
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/pixelart/image.rb', line 243 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
227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/pixelart/image.rb', line 227 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
210 |
# File 'lib/pixelart/image.rb', line 210 def []( x, y ) @img[x,y]; end |
#[]=(x, y, value) ⇒ Object
211 |
# File 'lib/pixelart/image.rb', line 211 def []=( x, y, value ) @img[x,y]=value; end |
#_change_colors!(img, color_map) ⇒ Object
174 175 176 177 178 179 180 181 182 |
# File 'lib/pixelart/image.rb', line 174 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
168 169 170 171 172 |
# File 'lib/pixelart/image.rb', line 168 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
164 165 166 |
# File 'lib/pixelart/image.rb', line 164 def _parse_colors( colors ) colors.map {|color| Color.parse( color ) } end |
#blur(blur = 2) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 |
# File 'lib/pixelart/blur.rb', line 5 def blur( blur=2 ) @img.save( MAGICK_INPUT ) MiniMagick::Tool::Magick.new do |magick| magick << MAGICK_INPUT magick.blur( "#{blur}x#{blur}" ) magick << MAGICK_OUTPUT end Image.read( MAGICK_OUTPUT ) end |
#change_colors(color_map) ⇒ Object Also known as: recolor
add replace_colors alias too? - why? why not?
119 120 121 122 123 124 125 126 127 |
# File 'lib/pixelart/image.rb', line 119 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
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/pixelart/image.rb', line 141 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!
201 202 203 |
# File 'lib/pixelart/image.rb', line 201 def compose!( other, x=0, y=0 ) @img.compose!( other.image, x, y ) ## note: "unwrap" inner image ref end |
#flip ⇒ Object Also known as: flip_horizontally
101 102 103 104 |
# File 'lib/pixelart/image.rb', line 101 def flip img = @img.flip Image.new( img.width, img.height, img ) end |
#grayscale ⇒ Object Also known as: greyscale
filter / effects
93 94 95 96 |
# File 'lib/pixelart/image.rb', line 93 def grayscale img = @img.grayscale Image.new( img.width, img.height, img ) end |
#height ⇒ Object
208 |
# File 'lib/pixelart/image.rb', line 208 def height() @img.height; end |
#image ⇒ Object
return image ref - use a different name - why? why not?
change to to_image - why? why not?
220 |
# File 'lib/pixelart/image.rb', line 220 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 |
#mirror ⇒ Object Also known as: flip_vertically, flop
107 108 109 110 |
# File 'lib/pixelart/image.rb', line 107 def mirror img = @img.mirror Image.new( img.width, img.height, img ) end |
#pixels ⇒ Object
213 |
# File 'lib/pixelart/image.rb', line 213 def pixels() @img.pixels; end |
#save(path, constraints = {}) ⇒ Object Also known as: write
(image) delegates
todo/check: add some more??
190 191 192 193 194 195 196 197 |
# File 'lib/pixelart/image.rb', line 190 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 |
#sketch(sketch = 4, line: 1) ⇒ Object
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/pixelart/sketch.rb', line 6 def sketch( sketch=4, line: 1 ) # todo: line - find a better name eg. line_strenght/width or such? width = @img.width*sketch + (@img.width+1)*line height = @img.height*sketch + (@img.height+1)*line puts " #{width}x#{height}" img = Image.new( width, height, Color::WHITE ) @img.width.times do |x| @img.height.times do |y| pixel = @img[x,y] ## get surrounding pixels - if "out-of-bound" use transparent (0) left = x == 0 ? Color::TRANSPARENT : @img[x-1,y] top = y == 0 ? Color::TRANSPARENT : @img[x ,y-1] if pixel != left ## draw vertical line (sketch+line*2).times do |n| line.times do |m| img[ x*sketch + line*x + m, n + y*sketch + line*y] = Color::BLACK end end end if pixel != top ## draw horizontal line (sketch+line*2).times do |n| line.times do |m| img[n + x*sketch + line*x, y*sketch + line*y + m] = Color::BLACK end end end ## check special edge case for x and y to add "finish-up" right and bottom line if x == @img.width-1 && pixel != Color::TRANSPARENT ## draw vertical line (sketch+line*2).times do |n| line.times do |m| img[ (x+1)*sketch + line*(x+1) + m, n + y*sketch + line*y] = Color::BLACK end end end if y== @img.height-1 && pixel != Color::TRANSPARENT ## draw horizontal line (sketch+line*2).times do |n| line.times do |m| img[n + x*sketch + line*x, (y+1)*sketch + line*(y+1) + m] = Color::BLACK end end end end end img end |
#spots(spot = 10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/pixelart/spots.rb', line 124 def spots( spot=10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false ) v = spots_hidef( spot, spacing: spacing, center: center, radius: radius, background: background, lightness: lightness, odd: odd ) v.to_image end |
#spots_hidef(spot = 10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false) ⇒ Object Also known as: spots_hd
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/pixelart/spots.rb', line 6 def spots_hidef( spot=10, spacing: 0, center: nil, radius: nil, background: nil, lightness: nil, odd: false ) width = @img.width*spot+(@img.width-1)*spacing height = @img.height*spot+(@img.height-1)*spacing ## puts " #{width}x#{height}" ## settings in a hash for "pretty printing" in comments settings = { spot: spot } settings[ :spacing ] = spacing if spacing settings[ :center ] = center if center settings[ :radius ] = radius if radius settings[ :background ] = background if background settings[ :lightness ] = lightness if lightness settings[ :odd ] = odd if odd v = Vector.new( width, height, header: <<TXT ) generated by pixelart/v#{VERSION} on #{Time.now.utc} spots_hidef with settings: #{settings.to_json} TXT min_center, max_center = center ? center : [0,0] min_radius, max_radius = radius ? radius : [0,0] ## note: allow passing in array of colors (will get randomally picked) background_colors = if background ## check for array; otherwise assume single color passed in background_ary = background.is_a?( Array) ? background : [background] background_ary.map { |background| Color.parse( background ) } else [0] # transparent (default - no background) end min_lightness, max_lightness = lightness ? lightness : [0.0,0.0] @img.width.times do |x| @img.height.times do |y| color = @img[ x, y ] if color == 0 ## transparent next if background.nil? color = if background_colors.size == 1 background_colors[0] else ## pick random background color background_colors[ rand( background_colors.size ) ] end end if lightness ## todo/check: make it work with alpha too h,s,l = Color.to_hsl( color, include_alpha: false ) h = h % 360 ## make sure h(ue) is always positive!!! ## note: rand() return between 0.0 and 1.0 l_diff = min_lightness + (max_lightness-min_lightness)*rand() lnew = [1.0, l+l_diff].min lnew = [0.0, lnew].max ## print " #{l}+#{l_diff}=#{lnew} " color = Color.from_hsl( h, [1.0, s].min, lnew ) end ## note: return hexstring with leading # # e.g. 0 becomes #00000000 # and so on color_hex = Color.to_hex( color, include_alpha: true ) cx_offset, cy_offset = if center ## randomize (offset off center +/-) [(spot/2 + min_center) + rand( max_center-min_center ), (spot/2 + min_center) + rand( max_center-min_center )] else [spot/2, ## center spot/2] end cx = x*spot + x*spacing + cx_offset cy = y*spot + y*spacing + cx_offset r = if radius ## randomize (radius +/-) min_radius + rand( max_radius-min_radius ) else spot/2 end cx += spot/2 if odd && (y % 2 == 1) ## add odd offset v.circle( cx: cx, cy: cy, r: r, fill: color_hex) end end v end |
#width ⇒ Object
207 |
# File 'lib/pixelart/image.rb', line 207 def width() @img.width; end |
#zoom(zoom = 2) ⇒ Object Also known as: scale
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/pixelart/image.rb', line 67 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 |