pixelart 0.1.6 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df39f58ffbca991ae1fbbdc2f4a3b83545074f7872dd96bf9b23ac5be7259a0d
4
- data.tar.gz: 865908e9a481ef8bd74c98572dbe6c17fd2c7dde2bd5f0f8613247716be4065a
3
+ metadata.gz: 3c2b93e1779a1c06ff0b13dbfcae471648cd2f2dc153b17da3fd864615f6f6c1
4
+ data.tar.gz: cef870177214b37eb902a99a41410dc8c080b613fed7c48cccd801290392fd28
5
5
  SHA512:
6
- metadata.gz: 65305e097f184eb6e62e6d0b392f2a9e42c2a705abbb198752fdb905681bc35ac9fb263efd663bb77d5ab3cef440127e55eeee2eb33ba841fde35338dff50d63
7
- data.tar.gz: 94df6b1fced1c835ae8132922350b9d50d2fe21c29c020cc2b4595246af2d42fc0e0346bcfd6aacbc1ae45e153d58cfb5592ee90ab9c9da01b4ce07313d2b23e
6
+ metadata.gz: 8ac5ad9fd3e16d9d490d7c43c38fa2b95ee6318a7c0f319e142fc59084ab70315ebb3d0e800d4536a098214768cab3adae99b1ec2c482ce93a4ab1e902d36cc5
7
+ data.tar.gz: 67e9e1214ae9f614a2694d30a9b63ce5aa2c83dad17a98dac141913b86cb4375ea22fc0630e2e3160f6b768b4b79ab2236555d0467471fd6a127f477ca48f6db
data/Manifest.txt CHANGED
@@ -5,8 +5,11 @@ Rakefile
5
5
  lib/pixelart.rb
6
6
  lib/pixelart/base.rb
7
7
  lib/pixelart/color.rb
8
+ lib/pixelart/composite.rb
8
9
  lib/pixelart/gradient.rb
9
10
  lib/pixelart/image.rb
11
+ lib/pixelart/led.rb
10
12
  lib/pixelart/misc.rb
11
13
  lib/pixelart/palette.rb
14
+ lib/pixelart/pixelator.rb
12
15
  lib/pixelart/version.rb
data/lib/pixelart/base.rb CHANGED
@@ -14,9 +14,16 @@ require 'pixelart/color'
14
14
  require 'pixelart/gradient'
15
15
  require 'pixelart/palette'
16
16
  require 'pixelart/image'
17
+ require 'pixelart/composite'
18
+
19
+ require 'pixelart/pixelator'
17
20
 
18
21
  require 'pixelart/misc' ## misc helpers
19
22
 
23
+ require 'pixelart/led' ## (special) effects / filters
24
+
25
+
26
+
20
27
 
21
28
  ##########
22
29
  # add some spelling convenience variants
@@ -27,6 +34,8 @@ module Pixelart
27
34
 
28
35
  Palette256Image = Palette8BitImage = Palette8bitImage =
29
36
  ImagePalette256 = ImagePalette8Bit = ImagePalette8bit
37
+
38
+ CompositeImage = ImageComposite
30
39
  end
31
40
 
32
41
 
@@ -7,7 +7,6 @@ class Color
7
7
  WHITE = 0xffffffff # rgba(255,255,255,255)
8
8
 
9
9
 
10
-
11
10
  def self.parse( color )
12
11
  if color.is_a?( Integer ) ## e.g. assumes ChunkyPNG::Color.rgb() or such
13
12
  color ## pass through as is 1:1
@@ -0,0 +1,77 @@
1
+ module Pixelart
2
+
3
+ class ImageComposite < Image # check: (re)name to Collage, Sheet, Sprites, or such?
4
+
5
+ ## default tile width / height in pixel -- check: (re)name to sprite or such? why? why not?
6
+ TILE_WIDTH = 24
7
+ TILE_HEIGHT = 24
8
+
9
+
10
+ def initialize( *args, **kwargs )
11
+ @tile_width = kwargs[:width] || kwargs[:tile_width] || TILE_WIDTH
12
+ @tile_height = kwargs[:height] || kwargs[:tile_height] || TILE_HEIGHT
13
+
14
+ ## todo/fix: check type - args[0] is Image!!!
15
+ if args.size == 1 ## assume "copy" c'tor with passed in image
16
+ img = args[0] ## pass image through as-is
17
+
18
+ @tile_cols = img.width / @tile_width ## e.g. 2400/24 = 100
19
+ @tile_rows = img.height / @tile_height ## e.g. 2400/24 = 100
20
+ @tile_count = @tile_cols * @tile_rows ## ## 10000 = 100x100 (2400x2400 pixel)
21
+ elsif args.size == 2 || args.size == 0 ## cols, rows
22
+ ## todo/fix: check type - args[0] & args[1] is Integer!!!!!
23
+ ## todo/check - find a better name for cols/rows - why? why not?
24
+ @tile_cols = args[0] || 3
25
+ @tile_rows = args[1] || 3
26
+ @tile_count = 0 # (track) current index (of added images)
27
+
28
+ img = ChunkyPNG::Image.new( @tile_cols * @tile_width,
29
+ @tile_rows * @tile_height )
30
+ else
31
+ raise ArgumentError, "cols, rows or image arguments expected; got: #{args.inspect}"
32
+ end
33
+
34
+ puts " #{img.height}x#{img.width} (height x width)"
35
+
36
+ super( nil, nil, img )
37
+ end
38
+
39
+
40
+ def count() @tile_count; end
41
+ alias_method :size, :count ## add size alias (confusing if starting with 0?) - why? why not?
42
+
43
+ #####
44
+ # set / add tile
45
+
46
+ def add( image )
47
+ y, x = @tile_count.divmod( @tile_cols )
48
+
49
+ puts " [#{@tile_count}] @ (#{x}/#{y}) #{image.width}x#{image.height} (height x width)"
50
+
51
+ ## note: image.image - "unwrap" the "raw" ChunkyPNG::Image
52
+ @img.compose!( image.image, x*@tile_width, y*@tile_height )
53
+ @tile_count += 1
54
+ end
55
+ alias_method :<<, :add
56
+
57
+
58
+
59
+ ######
60
+ # get tile
61
+
62
+ def tile( index )
63
+ y, x = index.divmod( @tile_cols )
64
+ img = @img.crop( x*@tile_width, y*@tile_height, @tile_width, @tile_height )
65
+ Image.new( img.width, img.height, img ) ## wrap in pixelart image
66
+ end
67
+
68
+ def []( *args ) ## overload - why? why not?
69
+ if args.size == 1
70
+ index = args[0]
71
+ tile( index )
72
+ else
73
+ super ## e.g [x,y] --- get pixel
74
+ end
75
+ end
76
+ end # class ImageComposite
77
+ end # module Pixelart
@@ -9,7 +9,14 @@ def self.read( path ) ## convenience helper
9
9
  end
10
10
 
11
11
 
12
- def self.parse( pixels, colors: )
12
+
13
+ CHARS = '.@xo^~%*+=:' ## todo/check: rename to default chars or such? why? why not?
14
+
15
+ ## todo/check: support default chars encoding auto-of-the-box always
16
+ ## or require user-defined chars to be passed in - why? why not?
17
+ def self.parse( pixels, colors:, chars: CHARS )
18
+ has_keys = colors.is_a?(Hash) ## check if passed-in user-defined keys (via hash table)?
19
+
13
20
  colors = parse_colors( colors )
14
21
  pixels = parse_pixels( pixels )
15
22
 
@@ -20,7 +27,19 @@ def self.parse( pixels, colors: )
20
27
 
21
28
  pixels.each_with_index do |row,y|
22
29
  row.each_with_index do |color,x|
23
- pixel = colors[color]
30
+ pixel = if has_keys ## if passed-in user-defined keys check only the user-defined keys
31
+ colors[color]
32
+ else
33
+ ## try map ascii art char (.@xo etc.) to color index (0,1,2)
34
+ ## if no match found - fallback on assuming draw by number (0 1 2 etc.) encoding
35
+ pos = chars.index( color )
36
+ if pos
37
+ colors[ pos.to_s ]
38
+ else ## assume nil (not found)
39
+ colors[ color ]
40
+ end
41
+ end
42
+
24
43
  img[x,y] = pixel
25
44
  end # each row
26
45
  end # each data
@@ -31,6 +50,9 @@ end
31
50
 
32
51
 
33
52
  def initialize( width, height, initial=Color::TRANSPARENT )
53
+ ### todo/fix:
54
+ ## change params to *args only - why? why not?
55
+ ## make width/height optional if image passed in?
34
56
 
35
57
  if initial.is_a?( ChunkyPNG::Image )
36
58
  @img = initial
@@ -73,6 +95,13 @@ def grayscale
73
95
  Image.new( img.width, img.height, img )
74
96
  end
75
97
 
98
+ def mirror
99
+ img = @img.mirror
100
+ Image.new( img.width, img.height, img )
101
+ end
102
+ alias_method :flip_vertically, :mirror
103
+
104
+
76
105
  ## add replace_colors alias too? - why? why not?
77
106
  def change_colors( color_map )
78
107
  color_map = _parse_color_map( color_map )
@@ -170,6 +199,9 @@ def []=( x, y, value ) @img[x,y]=value; end
170
199
 
171
200
  def pixels() @img.pixels; end
172
201
 
202
+ ### todo/check: add colors() e.g. @img.pixels.uniq - why? why not?
203
+
204
+
173
205
  ## return image ref - use a different name - why? why not?
174
206
  ## change to to_image - why? why not?
175
207
  def image() @img; end
@@ -183,10 +215,7 @@ def self.parse_pixels( pixels )
183
215
  data = []
184
216
  pixels.each_line do |line|
185
217
  line = line.strip
186
- if line.empty?
187
- puts "!! WARN: skipping empty line in pixel art source"
188
- next
189
- end
218
+ next if line.start_with?( '#' ) || line.empty? ## note: allow comments and empty lines
190
219
 
191
220
  ## note: allow multiple spaces or tabs to separate pixel codes
192
221
  ## 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
@@ -197,12 +226,13 @@ def self.parse_pixels( pixels )
197
226
  end
198
227
 
199
228
 
229
+
200
230
  def self.parse_colors( colors )
201
231
  if colors.is_a?( Array ) ## convenience shortcut
202
232
  ## note: always auto-add color 0 as pre-defined transparent - why? why not?
203
233
  h = { '0' => Color::TRANSPARENT }
204
234
  colors.each_with_index do |color, i|
205
- h[ (i+1).to_s ] = Color.parse( color )
235
+ h[ (i+1).to_s ] = Color.parse( color )
206
236
  end
207
237
  h
208
238
  else ## assume hash table with color map
@@ -215,7 +245,6 @@ def self.parse_colors( colors )
215
245
  end
216
246
 
217
247
 
218
-
219
248
  end # class Image
220
249
  end # module Pixelart
221
250
 
@@ -0,0 +1,37 @@
1
+ module Pixelart
2
+
3
+
4
+ class Image
5
+ def led( led=8, spacing: 2, round_corner: false )
6
+
7
+ width = @img.width*led + (@img.width-1)*spacing
8
+ height = @img.height*led + (@img.height-1)*spacing
9
+
10
+ puts " #{width}x#{height}"
11
+
12
+ img = Image.new( width, height, Color::BLACK )
13
+
14
+ @img.width.times do |x|
15
+ @img.height.times do |y|
16
+ pixel = @img[x,y]
17
+ pixel = Color::BLACK if pixel == Color::TRANSPARENT
18
+ led.times do |n|
19
+ led.times do |m|
20
+ ## round a little - drop all four corners for now
21
+ next if round_corner &&
22
+ [[0,0],[0,1],[1,0],[1,1],[0,2],[2,0],
23
+ [0,led-1],[0,led-2],[1,led-1],[1,led-2],[0,led-3],[2,led-1],
24
+ [led-1,0],[led-1,1],[led-2,0],[led-2,1],[led-1,2],[led-3,0],
25
+ [led-1,led-1],[led-1,led-2],[led-2,led-1],[led-2,led-2],[led-1,led-3],[led-3,led-1],
26
+ ].include?( [n,m] )
27
+ img[x*led+n + spacing*x,
28
+ y*led+m + spacing*y] = pixel
29
+ end
30
+ end
31
+ end
32
+ end
33
+ img
34
+ end
35
+ end # class Image
36
+ end # module Pixelart
37
+
data/lib/pixelart/misc.rb CHANGED
@@ -15,6 +15,9 @@ class ImagePalette8bit < Image # or use Palette256 alias?
15
15
  img = ChunkyPNG::Image.new( 32*size+(32-1)*spacing,
16
16
  8*size+(8-1)*spacing )
17
17
 
18
+
19
+ colors =colors.map {|color| Color.parse( color ) }
20
+
18
21
  colors.each_with_index do |color,i|
19
22
  y,x = i.divmod( 32 )
20
23
  if size > 1
@@ -32,6 +35,32 @@ class ImagePalette8bit < Image # or use Palette256 alias?
32
35
  super( img.width, img.height, img )
33
36
  end
34
37
  end # class ImagePalette8bit
38
+
39
+
40
+
41
+ class ImageColorBar < Image
42
+ ## make a color bar
43
+ ## keep auto-zoom 24x or such - why? why not?
44
+ def initialize( colors, size: 24 )
45
+ img = ChunkyPNG::Image.new( colors.size*size,
46
+ size,
47
+ ChunkyPNG::Color::WHITE ) # why? why not?
48
+
49
+ colors = colors.map {|color| Color.parse( color ) }
50
+
51
+ colors.each_with_index do |color,i|
52
+ size.times do |x|
53
+ size.times do |y|
54
+ img[x+size*i,y] = color
55
+ end
56
+ end
57
+ end
58
+
59
+ super( img.width, img.height, img )
60
+ end
61
+ end # class ImageColorBar
62
+
63
+
35
64
  end # module Pixelart
36
65
 
37
66
 
@@ -0,0 +1,165 @@
1
+ module Pixelart
2
+
3
+
4
+ class Pixelator # or use Minifier or such - rename - why? why not?
5
+
6
+ def initialize( img, width=24, height=24 )
7
+ @img = img.is_a?( Image ) ? img.image : img ## "unwrap" if Pixelart::Image
8
+ @width = width
9
+ @height = height
10
+
11
+ ## calculate pixel size / density / resolution
12
+ ## how many pixels per pixel?
13
+ @xsize, @xoverflow = img.width.divmod( width )
14
+ @ysize, @yoverflow = img.height.divmod( height )
15
+
16
+ puts "minify image size from (#{@img.width}x#{@img.height}) to (#{width}x#{height})"
17
+ puts " pixel size (#{@xsize}x#{@ysize}) - #{@xsize*@ysize} pixel(s) per pixel"
18
+ puts " overflow x: #{@xoverflow}, y: #{@yoverflow} pixel(s)" if @xoverflow > 0 || @yoverflow > 0
19
+ end
20
+
21
+
22
+ def grid( spacing: 10 )
23
+ width = @img.width + (@width-1)*spacing
24
+ height = @img.height + (@height-1)*spacing
25
+
26
+ img = ChunkyPNG::Image.new( width, height, ChunkyPNG::Color::WHITE )
27
+
28
+ @img.width.times do |x|
29
+ xpixel = x/@xsize
30
+ @img.height.times do |y|
31
+ ypixel = y/@ysize
32
+
33
+ ## clip overflow pixels
34
+ xpixel = @width-1 if xpixel >= @width
35
+ ypixel = @height-1 if ypixel >= @height
36
+
37
+ color = @img[x,y]
38
+ img[x + spacing*xpixel,
39
+ y + spacing*ypixel] = color
40
+ end
41
+ end
42
+
43
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
44
+ end
45
+
46
+
47
+ # pixels by coordinates (x/y) with color statistics / usage
48
+ def pixels
49
+ @pixels ||= begin
50
+ pixels = []
51
+ @img.width.times do |x|
52
+ xpixel = x/@xsize
53
+ @img.height.times do |y|
54
+ ypixel = y/@ysize
55
+
56
+ ## skip/cut off overflow pixels
57
+ next if xpixel >= @width || ypixel >= @height
58
+
59
+ color = @img[x,y]
60
+ colors = pixels[xpixel+ypixel*@width] ||= Hash.new(0)
61
+ colors[ color ] += 1
62
+ end
63
+ end
64
+
65
+ ## sort pixel colors by usage / count (highest first)
66
+ pixels = pixels.map do |pixel|
67
+ pixel.sort do |l,r|
68
+ r[1] <=> l[1]
69
+ end.to_h
70
+ end
71
+ pixels
72
+ end
73
+ end
74
+
75
+ def pixel(x,y) pixels[x+y*@width]; end
76
+ alias_method :[], :pixel
77
+
78
+
79
+ def can_pixelate?( threshold: 50 )
80
+ # check if any pixel has NOT a color with a 50% majority?
81
+ count = 0
82
+ @width.times do |x|
83
+ @height.times do |y|
84
+ pixel = pixel( x, y )
85
+ sum = pixel.values.sum
86
+ color_count = pixel.values[0]
87
+
88
+ threshold_count = sum / (100/threshold)
89
+ if color_count < threshold_count
90
+ count += 1
91
+ puts "!! #{color_count} < #{threshold_count} (#{threshold}%)"
92
+ ## todo/check: stor warn in a public errors or warns array - why? why not?
93
+ puts "!! WARN #{count} - pixel (#{x}/#{y}) - no majority (#{threshold}%) color:"
94
+ pp pixel
95
+ end
96
+ end
97
+ end
98
+
99
+ count == 0 ## return true if not warnings found
100
+ end
101
+ alias_method :pixelate?, :can_pixelate?
102
+
103
+
104
+ def pixelate
105
+ img = ChunkyPNG::Image.new( @width, @height )
106
+
107
+ @width.times do |x|
108
+ @height.times do |y|
109
+ pixel = pixel( x, y )
110
+ color = pixel.keys[0]
111
+ img[x,y] = color
112
+ end
113
+ end
114
+
115
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
116
+ end
117
+
118
+ def outline
119
+ ## create a two color outline (transparent and non-transparent color)
120
+ img = ChunkyPNG::Image.new( @width, @height )
121
+
122
+ @width.times do |x|
123
+ @height.times do |y|
124
+ pixel = pixel( x, y )
125
+ ## calculate pixel count for transparent and non-transparent parts
126
+ ## note:
127
+ ## also count all colors with alpha channel < 200 to transparent!!
128
+ transparent_count, color_count = pixel.reduce([0,0]) do |mem, (color,count)|
129
+ hsl = Color.to_hsl( color )
130
+ ## get alpha channel (transparency) for hsla
131
+ ## 0-255 max.
132
+ alpha = hsl[3]
133
+ if color == 0x00 || alpha < 200
134
+ mem[0] += count
135
+ else
136
+ mem[1] += count
137
+ end
138
+ mem
139
+ end
140
+
141
+ print "."
142
+ if transparent_count > 0 && color_count > 0
143
+ print "(#{x}/#{y}=>#{transparent_count}/#{color_count})"
144
+ end
145
+
146
+ ## todo/check:
147
+ ## warn if sum_transparent == sum_color
148
+ ## or within "threshold" e.g. below 55% or 58% or such - why? why not?
149
+ ## or add treshold as param to outline?
150
+ color = if transparent_count > color_count
151
+ 0x0
152
+ else
153
+ 0x0000ffff ## use blue for now
154
+ end
155
+
156
+ img[x,y] = color
157
+ end
158
+ end
159
+ print "\n"
160
+
161
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
162
+ end
163
+ end # class Pixelator
164
+ end # module Pixelart
165
+
@@ -2,8 +2,8 @@
2
2
  module Pixelart
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 1
6
- PATCH = 6
5
+ MINOR = 2
6
+ PATCH = 2
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pixelart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-20 00:00:00.000000000 Z
11
+ date: 2021-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chunky_png
@@ -75,10 +75,13 @@ files:
75
75
  - lib/pixelart.rb
76
76
  - lib/pixelart/base.rb
77
77
  - lib/pixelart/color.rb
78
+ - lib/pixelart/composite.rb
78
79
  - lib/pixelart/gradient.rb
79
80
  - lib/pixelart/image.rb
81
+ - lib/pixelart/led.rb
80
82
  - lib/pixelart/misc.rb
81
83
  - lib/pixelart/palette.rb
84
+ - lib/pixelart/pixelator.rb
82
85
  - lib/pixelart/version.rb
83
86
  homepage: https://github.com/rubycoco/pixel
84
87
  licenses: