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) ⇒ 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(pixels) ⇒ Object
helpers.
-
.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.
99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/pixelart/image.rb', line 99 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) ⇒ 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 |
# File 'lib/pixelart/image.rb', line 54 def self.parse( pixels, colors:, background: Color::TRANSPARENT, 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 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
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/pixelart/image.rb', line 350 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
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/pixelart/image.rb', line 323 def self.parse_pixels( pixels ) if pixels.is_a?( Array ) ## assume array of string (lines) data = [] pixels.each do |line| ## convert (string) line into indidual chars data << line.each_char.reduce( [] ) { |mem, c| mem << c; mem } end data else ## assume it's a (multi-line) string (with newlines) ## assert and throw ArgumentError if not? - why? why not? 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 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
306 |
# File 'lib/pixelart/image.rb', line 306 def []( x, y ) @img[x,y]; end |
#[]=(x, y, value) ⇒ Object
307 |
# File 'lib/pixelart/image.rb', line 307 def []=( x, y, value ) @img[x,y]=value; end |
#_change_colors!(img, color_map) ⇒ Object
270 271 272 273 274 275 276 277 278 |
# File 'lib/pixelart/image.rb', line 270 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
264 265 266 267 268 |
# File 'lib/pixelart/image.rb', line 264 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
260 261 262 |
# File 'lib/pixelart/image.rb', line 260 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?
215 216 217 218 219 220 221 222 223 |
# File 'lib/pixelart/image.rb', line 215 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
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/pixelart/image.rb', line 237 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!
297 298 299 |
# File 'lib/pixelart/image.rb', line 297 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
139 140 141 142 |
# File 'lib/pixelart/image.rb', line 139 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?
177 178 179 180 |
# File 'lib/pixelart/image.rb', line 177 def flip_horizontally img = @img.flip_horizontally Image.new( img.width, img.height, img ) end |
#grayscale ⇒ Object Also known as: greyscale
filter / effects
162 163 164 165 |
# File 'lib/pixelart/image.rb', line 162 def grayscale img = @img.grayscale Image.new( img.width, img.height, img ) end |
#height ⇒ Object
304 |
# File 'lib/pixelart/image.rb', line 304 def height() @img.height; end |
#image ⇒ Object
return image ref - use a different name - why? why not?
change to to_image - why? why not?
316 |
# File 'lib/pixelart/image.rb', line 316 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)
146 147 148 149 150 |
# File 'lib/pixelart/image.rb', line 146 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
190 191 192 193 |
# File 'lib/pixelart/image.rb', line 190 def mirror img = @img.mirror Image.new( img.width, img.height, img ) end |
#pixels ⇒ Object
309 |
# File 'lib/pixelart/image.rb', line 309 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
206 207 208 209 |
# File 'lib/pixelart/image.rb', line 206 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
200 201 202 203 |
# File 'lib/pixelart/image.rb', line 200 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??
286 287 288 289 290 291 292 293 |
# File 'lib/pixelart/image.rb', line 286 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
153 154 155 |
# File 'lib/pixelart/image.rb', line 153 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
303 |
# File 'lib/pixelart/image.rb', line 303 def width() @img.width; end |
#zoom(zoom = 2, spacing: 0) ⇒ Object Also known as: scale
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/pixelart/image.rb', line 114 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 |