Class: Pixelart::Image
- Inherits:
-
Object
- Object
- Pixelart::Image
- Defined in:
- lib/pixelart/silhouette.rb,
lib/pixelart/led.rb,
lib/pixelart/blur.rb,
lib/pixelart/image.rb,
lib/pixelart/spots.rb,
lib/pixelart/circle.rb,
lib/pixelart/invert.rb,
lib/pixelart/sample.rb,
lib/pixelart/sketch.rb,
lib/pixelart/convert.rb,
lib/pixelart/stripes.rb,
lib/pixelart/ukraine.rb,
lib/pixelart/transparent.rb
Overview
todo/check:
use a different name for silhouette
- why not - outline ???
or - shadow ???
or - profile ???
or - figure ???
or - shape ???
or - form ???
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, }
- RAINBOW_RED =
todo/check: move colors to (reusable) constants int Color !!!! why? why not?
Color.parse( '#E40303' )
- RAINBOW_ORANGE =
Color.parse( '#FF8C00' )
- RAINBOW_YELLOW =
Color.parse( '#FFED00' )
- RAINBOW_GREEN =
Color.parse( '#008026' )
- RAINBOW_BLUE =
Color.parse( '#004DFF' )
- RAINBOW_VIOLET =
Color.parse( '#750787' )
- UKRAINE_BLUE =
todo/check: move colors to (reusable) constants int Color !!!! why? why not?
Color.parse( '#0057b7' )
- UKRAINE_YELLOW =
Color.parse( '#ffdd00' )
Class Method Summary collapse
- .blob(blob) ⇒ Object (also: from_blob)
- .calc_sample_steps(width, new_width, center: true, debug: false) ⇒ Object
- .calc_stripes(length, n: 2, debug: false) ⇒ Object
-
.convert(dir, from: 'jpg', to: 'png', outdir: nil, overwrite: true) ⇒ Object
helper to convert (all) image in directory chech: move to ImageUtils.convert or such - why? why not?.
- .inherited(subclass) ⇒ Object
-
.parse(pixels, colors:, background: Color::TRANSPARENT, chars: CHARS, width: nil, height: nil) ⇒ 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_base64(str) ⇒ Object
- .parse_colors(colors) ⇒ Object
-
.parse_pixels(obj) ⇒ Object
helpers.
- .parse_pixels_strict(rx, txt) ⇒ Object
-
.read(path) ⇒ Object
convenience helper.
-
.subclasses ⇒ Object
keep track of all (inherited) subclasses via inherited hook.
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)
- #circle ⇒ Object
- #compose!(other, x = 0, y = 0) ⇒ Object (also: #paste!)
- #crop(x, y, crop_width, crop_height) ⇒ Object
-
#flip_horizontally ⇒ Object
(also: #flop)
flip horizontally on x-axis (top-to-bottom/bottom-to-top) e.g.
-
#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.
-
#invert ⇒ Object
note: invert will only invert r/g/b - and NOT the a(lpha) channel the a(lpha) channel get passed on as is (1:1).
- #led(led = 8, spacing: 2, round_corner: false) ⇒ Object
-
#left(left) ⇒ Object
shift image n-pixels to the left (NOT changing width/height).
-
#mirror ⇒ Object
(also: #flip_vertically, #flip, #phlip, #hand_phlip)
flip vertially on y-axis (right-to-left/left-to-right) e.g.
- #pixels ⇒ Object
- #rainbow ⇒ Object (also: #pride)
-
#rotate_clockwise ⇒ Object
(also: #rotate_right)
90 degrees.
-
#rotate_counter_clockwise ⇒ Object
(also: #rotate_left)
90 degrees.
-
#sample(steps_x, steps_y = steps_x, top_x: 0, top_y: 0) ⇒ Object
(also: #pixelate)
todo/check: rename to sample to resample or downsample - why? why not?.
- #sample_debug(steps_x, steps_y = steps_x, color: Color.parse( '#ffff00' ), top_x: 0, top_y: 0) ⇒ Object (also: #pixelate_debug)
-
#save(path, constraints = {}) ⇒ Object
(also: #write)
(image) delegates todo/check: add some more??.
- #silhouette(color = '#000000') ⇒ Object
- #sketch(sketch = 4, line: 1, line_color: Color::BLACK, colorize: false) ⇒ 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)
- #stripes_horizontal(*colors) ⇒ Object (also: #stripes)
- #to_blob ⇒ Object (also: #blob)
- #transparent(style = :solid, fuzzy: false) ⇒ Object
- #ukraine ⇒ Object
- #width ⇒ Object
- #zoom(zoom = 2, spacing: 0) ⇒ Object (also: #scale)
Constructor Details
#initialize(width, height, initial = Color::TRANSPARENT) ⇒ Image
Returns a new instance of Image.
127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/pixelart/image.rb', line 127 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
.blob(blob) ⇒ Object Also known as: from_blob
37 38 39 40 |
# File 'lib/pixelart/image.rb', line 37 def self.blob( blob ) img_inner = ChunkyPNG::Image.from_blob( blob ) new( img_inner.width, img_inner.height, img_inner ) end |
.calc_sample_steps(width, new_width, center: true, debug: 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 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 |
# File 'lib/pixelart/sample.rb', line 5 def self.calc_sample_steps( width, new_width, center: true, debug: false ) ## todo/fix: assert new_width is smaller than width if debug puts puts "==> from: #{width}px to: #{new_width}px" end indexes = [] base_step = width / new_width ## pixels per pixel err_step = (width % new_width) * 2 ## multiply by 2 denominator = new_width * 2 # denominator (in de - nenner e.g. 1/nenner 4/nenner) overflow = err_step*new_width/denominator ## todo/check - assert that div is always WITHOUT remainder!!!!! if debug puts puts "base_step (pixels per pixel):" puts " #{base_step} - #{base_step} * #{new_width}px = #{base_step*new_width}px" puts "err_step (in 1/#{width}*2):" puts " #{err_step} / #{denominator} - #{err_step*new_width} / #{denominator} = +#{err_step*new_width/denominator}px overflow" puts end # initial pixel offset index = 0 err = err_step/2 ## note: start off with +err_step/2 to add overflow pixel in the "middle" index += if center.is_a?( Integer ) center elsif center base_step/2 else 0 # use 0px offset end new_width.times do |i| if err >= denominator ## overflow puts " -- overflow #{err}/#{denominator} - add +1 pixel offset to #{i}" if debug index += 1 err -= denominator end puts " #{i} => #{index} -- #{err} / #{denominator}" if debug indexes[i] = index index += base_step err += err_step end indexes end |
.calc_stripes(length, n: 2, debug: 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/pixelart/stripes.rb', line 5 def self.calc_stripes( length, n: 2, debug: false ) stripes = [] base_step = length / n ## pixels per pixel err_step = (length % n) * 2 ## multiply by 2 denominator = n * 2 # denominator (in de - nenner e.g. 1/nenner 4/nenner) overflow = err_step*n/denominator ## todo/check - assert that div is always WITHOUT remainder!!!!! if debug puts puts "base_step (pixels per stripe):" puts " #{base_step} - #{base_step}px * #{n} = #{base_step*n}px" puts "err_step (in 1/#{length}*2):" puts " #{err_step} / #{denominator} - #{err_step*n} / #{denominator} = +#{err_step*n/denominator}px overflow" puts end err = 0 stripe = 0 n.times do |i| stripe = base_step err += err_step if err >= denominator ## overflow puts " -- overflow #{err}/#{denominator} - add +1 pixel to stripe #{i}" if debug stripe += 1 err -= denominator end puts " #{i} => #{stripe} -- #{err} / #{denominator}" if debug stripes[i] = stripe end ## note: assert calculation - sum of stripes MUST be equal length sum = stripes.sum puts " sum: #{sum}" if debug if sum != length puts "!! ERROR - stripes sum #{sum} calculation failed; expected #{length}:" pp stripes exit 1 end stripes end |
.convert(dir, from: 'jpg', to: 'png', outdir: nil, overwrite: true) ⇒ Object
helper to convert (all) image in directory
chech: move to ImageUtils.convert or such - why? why not?
what about the name e.g. rename to convert_dir or
batch_convert such - why? why not?
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 |
# File 'lib/pixelart/convert.rb', line 11 def self.convert( dir, from: 'jpg', to: 'png', outdir: nil, overwrite: true ) outdir = dir if outdir.nil? files = Dir.glob( "#{dir}/*.#{from}" ) puts "==> found #{files.size} image(s) to convert from #{from} to #{to} (overwrite mode set to: #{overwrite})" files.each_with_index do |file,i| dirname = File.dirname( file ) extname = File.extname( file ) basename = File.basename( file, extname ) ## skip convert if target / dest file already exists next if overwrite == false && File.exist?( "#{outdir}/#{basename}.#{to}" ) ## note: make sure outdir exists (magic will not create it??) FileUtils.mkdir_p( outdir ) unless Dir.exist?( outdir ) cmd = "magick convert #{dirname}/#{basename}.#{from} #{outdir}/#{basename}.#{to}" puts " [#{i+1}/#{files.size}] - #{cmd}" ## todo/fix: check return value!!! magick comand not available (in path) and so on!!! system( cmd ) if from == 'gif' ## check for multi-images for gif ## save image-0.png to image.png path0 = "#{outdir}/#{basename}-0.#{to}" path = "#{outdir}/#{basename}.#{to}" ## note: image-0.png only exists (gets generated) for multi-images if File.exist?( path0 ) puts " saving #{path0} to #{path}..." blob = File.open( path0, 'rb' ) { |f| f.read } File.open( path, 'wb' ) { |f| f.write( blob ) } end end end end |
.inherited(subclass) ⇒ Object
19 20 21 |
# File 'lib/pixelart/image.rb', line 19 def self.inherited( subclass ) subclasses << subclass end |
.parse(pixels, colors:, background: Color::TRANSPARENT, chars: CHARS, width: nil, height: nil) ⇒ Object
todo/check: support default chars encoding auto-of-the-box always
or require user-defined chars to be passed in - why? why not?
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 121 122 123 |
# File 'lib/pixelart/image.rb', line 54 def self.parse( pixels, colors:, background: Color::TRANSPARENT, chars: CHARS, width: nil, height: nil ) has_keys = colors.is_a?(Hash) ## check if passed-in user-defined keys (via hash table)? colors = parse_colors( colors ) ## note: for now use strict parser only ## if colors with hash map / keys defined ## will raise error / exit if unknown token found!!! ## AND pixels is a single txt / text string to parse (NOT array of string lines) ## # # note default for now is: # 1) tokens separated by space if not strict (e.g. has no color keys AND not array of strings) # 2) every char is a token if array of strings pixels = if has_keys && pixels.is_a?( String ) keys = colors.keys.map { |key| key.to_s } ## todo/fix: - sort by lenght first; ## - escape for rx chars!! rx = /#{keys.join('|')}/ parse_pixels_strict( rx, pixels ) else parse_pixels( pixels ) end ## note: for now only use (require) width for flattened/streamed text input if width ## always flattern first - why? why not? ## allow multi-line text inputs - allow/support why? why not? pixels = pixels.flatten.each_slice( width ).to_a else ## find row with max width width = pixels.reduce(1) {|width,row| row.size > width ? row.size : width } height = pixels.size end background = Color.parse( background ) unless background.is_a?( Integer ) 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] = if background && background != Color::TRANSPARENT && pixel == Color::TRANSPARENT background ## note: auto-fill transparent with background color else pixel end end # each row end # each data img end |
.parse_base64(str) ⇒ Object
31 32 33 34 35 |
# File 'lib/pixelart/image.rb', line 31 def self.parse_base64( str ) blob = Base64.decode64( str ) img_inner = ChunkyPNG::Image.from_blob( blob ) new( img_inner.width, img_inner.height, img_inner ) end |
.parse_colors(colors) ⇒ Object
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/pixelart/image.rb', line 406 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(obj) ⇒ Object
helpers
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/pixelart/image.rb', line 351 def self.parse_pixels( obj ) pixels = [] if obj.is_a?( Array ) ## assume array of string (lines) lines = obj lines.each do |line| ## convert (string) line into indidual chars pixels << line.each_char.reduce( [] ) { |mem, c| mem << c; mem } end else ## assume it's a (multi-line) string (with newlines) ## assert and throw ArgumentError if not? - why? why not? txt = obj txt.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 pixels << line.split( /[ \t]+/ ) end end pixels end |
.parse_pixels_strict(rx, txt) ⇒ Object
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/pixelart/image.rb', line 377 def self.parse_pixels_strict( rx, txt ) ## must match tokens in regex (rx) e.g. /0|1|2|3../ or /A|B|C... etc./ pixels = [] txt.each_line do |line| line = line.strip next if line.start_with?( '#' ) || line.empty? ## note: allow comments and empty lines scan = StringScanner.new( line ) tokens = [] loop do # puts " pos: #{scan.pos} - size: #{scan.rest.size} - #{scan.rest}" token = scan.scan( rx ) if token.nil? ## todo/fix: raise an exception here puts "!! ERROR - parse error; expected match of #{rx.to_s} but got: #{scan.rest}" exit 1 end tokens << token scan.skip( /[ \t]+/ ) break if scan.eos? end pixels << tokens end pixels end |
.read(path) ⇒ Object
convenience helper
26 27 28 29 |
# File 'lib/pixelart/image.rb', line 26 def self.read( path ) ## convenience helper img_inner = ChunkyPNG::Image.from_file( path ) new( img_inner.width, img_inner.height, img_inner ) end |
.subclasses ⇒ Object
keep track of all (inherited) subclasses via inherited hook
change/rename to descendants - why? why not?
note about rails (activesupport?)
If you use rails >= 3, you have two options in place.
Use .descendants if you want more than one level depth of children classes,
or use .subclasses for the first level of child classes.
15 16 17 |
# File 'lib/pixelart/image.rb', line 15 def self.subclasses @subclasses ||= [] end |
Instance Method Details
#[](x, y) ⇒ Object
334 |
# File 'lib/pixelart/image.rb', line 334 def []( x, y ) @img[x,y]; end |
#[]=(x, y, value) ⇒ Object
335 |
# File 'lib/pixelart/image.rb', line 335 def []=( x, y, value ) @img[x,y]=value; end |
#_change_colors!(img, color_map) ⇒ Object
298 299 300 301 302 303 304 305 306 |
# File 'lib/pixelart/image.rb', line 298 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
292 293 294 295 296 |
# File 'lib/pixelart/image.rb', line 292 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
288 289 290 |
# File 'lib/pixelart/image.rb', line 288 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?
243 244 245 246 247 248 249 250 251 |
# File 'lib/pixelart/image.rb', line 243 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
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/pixelart/image.rb', line 265 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 |
#circle ⇒ Object
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 |
# File 'lib/pixelart/circle.rb', line 10 def circle ### for radius use min of width / height r = [@img.width, @img.height].min / 2 center_x = width / 2 center_y = height / 2 ################ # try with 96x96 # center_x: 96 / 2 = 48 # center_y: 96 / 2 = 48 # # r: 96 / 2 = 48 img = Image.new( @img.width, @img.height ) @img.width.times do |x| @img.height.times do |y| ## change to float calcuation (instead of ints) - why? why not? xx, yy, rr = x - center_x, y - center_y, r img[ x, y] = if xx*xx+yy*yy < rr*rr @img[ x, y ] else 0 ## transparent - alpha(0) end end end img end |
#compose!(other, x = 0, y = 0) ⇒ Object Also known as: paste!
325 326 327 |
# File 'lib/pixelart/image.rb', line 325 def compose!( other, x=0, y=0 ) @img.compose!( other.image, x, y ) ## note: "unwrap" inner image ref end |
#crop(x, y, crop_width, crop_height) ⇒ Object
167 168 169 170 |
# File 'lib/pixelart/image.rb', line 167 def crop( x, y, crop_width, crop_height ) Image.new( nil, nil, image.crop( x,y, crop_width, crop_height ) ) end |
#flip_horizontally ⇒ Object Also known as: flop
flip horizontally on x-axis (top-to-bottom/bottom-to-top)
e.g. pixels on the top will now be pixels on the bottom
todo/check: commom use is reverse?
e.g. flip_vertically is top-to-bottom!!!
use flip_y_axis, flip_x_axis or something else - why? why not?
check photoshop and gimp terminology and update later if different - why? why not?
205 206 207 208 |
# File 'lib/pixelart/image.rb', line 205 def flip_horizontally img = @img.flip_horizontally Image.new( img.width, img.height, img ) end |
#grayscale ⇒ Object Also known as: greyscale
filter / effects
190 191 192 193 |
# File 'lib/pixelart/image.rb', line 190 def grayscale img = @img.grayscale Image.new( img.width, img.height, img ) end |
#height ⇒ Object
332 |
# File 'lib/pixelart/image.rb', line 332 def height() @img.height; end |
#image ⇒ Object
return image ref - use a different name - why? why not?
change to to_image - why? why not?
344 |
# File 'lib/pixelart/image.rb', line 344 def image() @img; end |
#invert ⇒ Object
note: invert will only invert r/g/b - and NOT the a(lpha) channel
the a(lpha) channel get passed on as is (1:1)
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 |
# File 'lib/pixelart/invert.rb', line 12 def invert img = Image.new( @img.width, @img.height ) @img.width.times do |x| @img.height.times do |y| pixel = @img[x,y] ## note: xor (^) with 0 returns the original value unmodified. ## xor (^) with 0xff flips the bits. ## that is we are flipping/inverting r, g and b. ## and keep the a(lpha) channel as is. ## hack - why? why not? ## if transparent e.g. 0x0 than keep as is ## do not use 0xffffff00 - makes a difference? img[x,y] = if pixel == Color::TRANSPARENT # transparent (0) Color::TRANSPARENT else pixel ^ 0xffffff00 end end end 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 |
#left(left) ⇒ Object
shift image n-pixels to the left (NOT changing width/height)
174 175 176 177 178 |
# File 'lib/pixelart/image.rb', line 174 def left( left ) img = Image.new( width, height ) img.compose!( crop( 0, 0, width-left, height ), left, 0 ) img end |
#mirror ⇒ Object Also known as: flip_vertically, flip, phlip, hand_phlip
flip vertially on y-axis (right-to-left/left-to-right)
e.g. pixels on the left will now be pixels on the right
218 219 220 221 |
# File 'lib/pixelart/image.rb', line 218 def mirror img = @img.mirror Image.new( img.width, img.height, img ) end |
#pixels ⇒ Object
337 |
# File 'lib/pixelart/image.rb', line 337 def pixels() @img.pixels; end |
#rainbow ⇒ Object Also known as: pride
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/pixelart/stripes.rb', line 97 def rainbow ## # the most common variant consists of six stripes: # red, orange, yellow, green, blue, and violet. # The flag is typically flown horizontally, # with the red stripe on top, as it would be in a natural rainbow # # see https://en.wikipedia.org/wiki/Rainbow_flag_(LGBT) stripes( RAINBOW_RED, RAINBOW_ORANGE, RAINBOW_YELLOW, RAINBOW_GREEN, RAINBOW_BLUE, RAINBOW_VIOLET ) end |
#rotate_clockwise ⇒ Object Also known as: rotate_right
90 degrees
234 235 236 237 |
# File 'lib/pixelart/image.rb', line 234 def rotate_clockwise # 90 degrees img = @img.rotate_clockwise Image.new( img.width, img.height, img ) end |
#rotate_counter_clockwise ⇒ Object Also known as: rotate_left
90 degrees
228 229 230 231 |
# File 'lib/pixelart/image.rb', line 228 def rotate_counter_clockwise # 90 degrees img = @img.rotate_counter_clockwise Image.new( img.width, img.height, img ) end |
#sample(steps_x, steps_y = steps_x, top_x: 0, top_y: 0) ⇒ Object Also known as: pixelate
todo/check: rename to sample to resample or downsample - why? why not?
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/pixelart/sample.rb', line 68 def sample( steps_x, steps_y=steps_x, top_x: 0, top_y: 0 ) width = steps_x.size height = steps_y.size puts " downsampling from #{self.width}x#{self.height} to #{width}x#{height}..." dest = Image.new( width, height ) steps_x.each_with_index do |step_x, x| steps_y.each_with_index do |step_y, y| pixel = self[top_x+step_x, top_y+step_y] dest[x,y] = pixel end end dest end |
#sample_debug(steps_x, steps_y = steps_x, color: Color.parse( '#ffff00' ), top_x: 0, top_y: 0) ⇒ Object Also known as: pixelate_debug
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 |
# File 'lib/pixelart/sample.rb', line 89 def sample_debug( steps_x, steps_y=steps_x, color: Color.parse( '#ffff00' ), top_x: 0, top_y: 0) ## add a yellow pixel ## todo/fix: get a clone of the image (DO NOT modify in place) img = self steps_x.each_with_index do |step_x, x| steps_y.each_with_index do |step_y, y| base_x = top_x+step_x base_y = top_y+step_y img[base_x,base_y] = color ## add more colors img[base_x+1,base_y] = color img[base_x+2,base_y] = color img[base_x,base_y+1] = color img[base_x,base_y+2] = color end end self end |
#save(path, constraints = {}) ⇒ Object Also known as: write
(image) delegates
todo/check: add some more??
314 315 316 317 318 319 320 321 |
# File 'lib/pixelart/image.rb', line 314 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 |
#silhouette(color = '#000000') ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/pixelart/silhouette.rb', line 14 def silhouette( color='#000000' ) color = Color.parse( color ) img = Image.new( @img.width, @img.height ) @img.width.times do |x| @img.height.times do |y| pixel = @img[x,y] img[x,y] = if pixel == Color::TRANSPARENT # transparent (0) Color::TRANSPARENT else color end end end img end |
#sketch(sketch = 4, line: 1, line_color: Color::BLACK, colorize: false) ⇒ 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 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 121 |
# File 'lib/pixelart/sketch.rb', line 6 def sketch( sketch=4, line: 1, line_color: Color::BLACK, colorize: false ) ## todo/check: rename color option to fill or such - why? why not? # 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}" background_color = colorize ? Color::TRANSPARENT : Color::WHITE img = Image.new( width, height, background_color ) @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] diag = (x==0 || y== 0) ? Color::TRANSPARENT : @img[x-1,y-1] if pixel != left ## draw vertical line line.times do |n| (sketch+line*2).times do |m| img[ x*sketch + line*x + n, m + y*sketch + line*y] = line_color 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] = line_color 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 line.times do |n| (sketch+line*2).times do |m| img[ (x+1)*sketch + line*(x+1) + n, m + y*sketch + line*y] = line_color 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] = line_color end end end ############### ## fill with pixel color if color true (default is false) if colorize && pixel != Color::TRANSPARENT sketch.times do |n| sketch.times do |m| img[x*sketch + line*(x+1) + n, y*sketch + line*(y+1) + m] = pixel end end if pixel == left ## draw vertical line line.times do |n| sketch.times do |m| img[x*sketch + line*x + n, y*sketch + line*(y+1) + m] = pixel # (y%2==0 ? 0x0000ffff : 0x000088ff ) # (for debugging) end end end if pixel == top ## draw horizontal line sketch.times do |n| line.times do |m| img[x*sketch + line*(x+1) + n, y*sketch + line*y + m] = pixel # (x%2==0 ? 0xff0000ff : 0x880000ff ) # (for debugging) end end end ## check all four same color (00,01) ## (10, x) - bingo! if pixel == left && pixel == top && pixel == diag line.times do |n| line.times do |m| img[x*sketch + line*x + n, y*sketch + line*y + m] = pixel # 0xffff00ff # (for debugging) end end end end # colorize? end # height.times end # width.times 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 |
#stripes_horizontal(*colors) ⇒ Object Also known as: stripes
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 |
# File 'lib/pixelart/stripes.rb', line 58 def stripes_horizontal( *colors ) colors = colors.map { |color| Color.parse( color ) } img = Image.new( @img.width, @img.height ) n = colors.size lengths = self.class.calc_stripes( @img.height, n: n ) i = 0 length = lengths[0] color = colors[0] @img.height.times do |y| if y >= length i += 1 length += lengths[i] color = colors[i] end @img.width.times do |x| img[x,y] = color end end img.compose!( self ) ## paste/compose image onto backgorund img end |
#to_blob ⇒ Object Also known as: blob
181 182 183 |
# File 'lib/pixelart/image.rb', line 181 def to_blob @img.to_blob end |
#transparent(style = :solid, fuzzy: 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/pixelart/transparent.rb', line 5 def transparent( style = :solid, fuzzy: false ) img = Image.new( width, height ) background = self[0,0] bh,bs,bl = Color.to_hsl( background ) bh = (bh % 360) ## might might negative degree (always make positive) height.times do |y| if style == :linear background = self[0,y] bh,bs,bl = Color.to_hsl( background ) bh = (bh % 360) ## might might negative degree (always make positive) end width.times do |x| pixel = self[x,y] if background == 0 ## special case if background is already transparent keep going img[x,y] = pixel elsif fuzzy ## check for more transparents ## not s is 0.0 to 0.99 (100%) ## and l is 0.0 to 0.99 (100%) h,s,l = Color.to_hsl( pixel ) h = (h % 360) ## might might negative degree (always make positive) ## try some kind-of fuzzy "heuristic" match on background color if ((h >= bh-5) && (h <= bh+5)) && ((s >= bs-0.07) && (s <= bs+0.07)) && ((l >= bl-0.07) && (l <= bl+0.07)) img[x,y] = 0 ## Color::TRANSPARENT if h != bh || s != bs || l != bl # report fuzzy background color puts " #{x}/#{y} fuzzy background #{[h,s,l]} ~= #{[bh,bs,bl]}" end else img[x,y] = pixel end else if pixel == background img[x,y] = 0 ## Color::TRANSPARENT else img[x,y] = pixel end end end end img end |
#ukraine ⇒ Object
16 |
# File 'lib/pixelart/ukraine.rb', line 16 def ukraine() stripes( UKRAINE_BLUE, UKRAINE_YELLOW ); end |
#width ⇒ Object
331 |
# File 'lib/pixelart/image.rb', line 331 def width() @img.width; end |
#zoom(zoom = 2, spacing: 0) ⇒ Object Also known as: scale
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/pixelart/image.rb', line 142 def zoom( zoom=2, spacing: 0 ) ## create a new zoom factor x image (2x, 3x, etc.) width = @img.width*zoom+(@img.width-1)*spacing height = @img.height*zoom+(@img.height-1)*spacing img = Image.new( width, height ) @img.width.times do |x| @img.height.times do |y| pixel = @img[x,y] zoom.times do |n| zoom.times do |m| img[n+zoom*x+spacing*x, m+zoom*y+spacing*y] = pixel end end end # each x end # each y img end |