pixelart 1.1.0 → 1.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 +4 -4
- data/Manifest.txt +3 -0
- data/README.md +8 -9
- data/Rakefile +2 -1
- data/lib/pixelart/base.rb +11 -1
- data/lib/pixelart/composite.rb +81 -4
- data/lib/pixelart/image.rb +28 -6
- data/lib/pixelart/silhouette.rb +35 -0
- data/lib/pixelart/spots.rb +70 -53
- data/lib/pixelart/transparent.rb +60 -0
- data/lib/pixelart/vector.rb +163 -0
- data/lib/pixelart/version.rb +2 -2
- data/lib/pixelart.rb +2 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6be97cd0f30e95965b6ca21d69142283d1e2869ba99e37b84245b5d95c47fda0
|
4
|
+
data.tar.gz: 2bc68cf7a70d718e2d96b92f4401f44074b01282cb5ede85d548b36edac7d3d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c329166bd2144e1e440d01447c9babc6d999de0aed2d9eb3dedaa521b016b829f0039d8fdf5475afeffbc46f2f75f1efaca1a95f73a3c3e968d653041ff4656a
|
7
|
+
data.tar.gz: 44ab750a9bdec98770ff6ed325b8fac5883e693ae3356b08012184bdf72a20c1c88a023fb883dc437311df043d6a2d23012f54309d70dfa8e3402deb5683e945
|
data/Manifest.txt
CHANGED
@@ -13,6 +13,9 @@ lib/pixelart/led.rb
|
|
13
13
|
lib/pixelart/misc.rb
|
14
14
|
lib/pixelart/palette.rb
|
15
15
|
lib/pixelart/pixelator.rb
|
16
|
+
lib/pixelart/silhouette.rb
|
16
17
|
lib/pixelart/sketch.rb
|
17
18
|
lib/pixelart/spots.rb
|
19
|
+
lib/pixelart/transparent.rb
|
20
|
+
lib/pixelart/vector.rb
|
18
21
|
lib/pixelart/version.rb
|
data/README.md
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
pixelart - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes
|
4
4
|
|
5
5
|
|
6
|
-
* home :: [github.com/
|
7
|
-
* bugs :: [github.com/
|
6
|
+
* home :: [github.com/pixelartexchange/pixel](https://github.com/pixelartexchange/pixel)
|
7
|
+
* bugs :: [github.com/pixelartexchange/pixel/issues](https://github.com/pixelartexchange/pixel/issues)
|
8
8
|
* gem :: [rubygems.org/gems/pixelart](https://rubygems.org/gems/pixelart)
|
9
9
|
* rdoc :: [rubydoc.info/gems/pixelart](http://rubydoc.info/gems/pixelart)
|
10
10
|
|
@@ -71,8 +71,8 @@ img3x.save( './i/mooncat_white-3x.png' )
|
|
71
71
|
|
72
72
|
Voila!
|
73
73
|
|
74
|
-

|
75
|
+

|
76
76
|
|
77
77
|
|
78
78
|
|
@@ -100,8 +100,8 @@ img3x.save( './i/mooncat_black-3x.png' )
|
|
100
100
|
|
101
101
|
Voila! Black is the new White!
|
102
102
|
|
103
|
-

|
104
|
+

|
105
105
|
|
106
106
|
|
107
107
|
|
@@ -227,8 +227,8 @@ img5x.save( './i/vader5x.png' )
|
|
227
227
|
|
228
228
|
Voila!
|
229
229
|
|
230
|
-

|
231
|
+

|
232
232
|
|
233
233
|
|
234
234
|
|
@@ -288,4 +288,3 @@ Just install the gem:
|
|
288
288
|
|
289
289
|
The scripts are dedicated to the public domain.
|
290
290
|
Use it as you please with no restrictions whatsoever.
|
291
|
-
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ Hoe.spec 'pixelart' do
|
|
8
8
|
self.summary = "pixelart - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes"
|
9
9
|
self.description = summary
|
10
10
|
|
11
|
-
self.urls = { home: 'https://github.com/
|
11
|
+
self.urls = { home: 'https://github.com/pixelartexchange/pixel' }
|
12
12
|
|
13
13
|
self.author = 'Gerald Bauer'
|
14
14
|
self.email = 'wwwmake@googlegroups.com'
|
@@ -20,6 +20,7 @@ Hoe.spec 'pixelart' do
|
|
20
20
|
self.extra_deps = [
|
21
21
|
['chunky_png'],
|
22
22
|
['mini_magick'],
|
23
|
+
['csvreader'],
|
23
24
|
]
|
24
25
|
|
25
26
|
self.licenses = ['Public Domain']
|
data/lib/pixelart/base.rb
CHANGED
@@ -7,6 +7,9 @@ require 'chunky_png'
|
|
7
7
|
require 'mini_magick'
|
8
8
|
|
9
9
|
|
10
|
+
# bonus / prologue / convenience 3rd party
|
11
|
+
require 'csvreader'
|
12
|
+
|
10
13
|
|
11
14
|
## stdlib
|
12
15
|
require 'pp'
|
@@ -28,14 +31,18 @@ require 'pixelart/palette'
|
|
28
31
|
require 'pixelart/image'
|
29
32
|
require 'pixelart/composite'
|
30
33
|
|
34
|
+
|
31
35
|
require 'pixelart/pixelator'
|
32
36
|
|
33
37
|
require 'pixelart/misc' ## misc helpers
|
34
38
|
|
35
39
|
#########################
|
36
|
-
# (special) effects / filters
|
40
|
+
# (special) effects / filters / etc
|
37
41
|
require 'pixelart/led'
|
38
42
|
require 'pixelart/sketch'
|
43
|
+
require 'pixelart/transparent'
|
44
|
+
require 'pixelart/silhouette'
|
45
|
+
|
39
46
|
|
40
47
|
## (special) effects / filters that require imagemagick
|
41
48
|
|
@@ -47,6 +54,9 @@ module Pixelart
|
|
47
54
|
MAGICK_OUTPUT = './tmp/magick_output.png'
|
48
55
|
end
|
49
56
|
|
57
|
+
require 'pixelart/vector' ## vector graphics helpers
|
58
|
+
|
59
|
+
|
50
60
|
require 'pixelart/spots'
|
51
61
|
require 'pixelart/blur'
|
52
62
|
|
data/lib/pixelart/composite.rb
CHANGED
@@ -7,10 +7,31 @@ class ImageComposite < Image # check: (re)name to Collage, Sheet, Sprites, or s
|
|
7
7
|
TILE_HEIGHT = 24
|
8
8
|
|
9
9
|
|
10
|
+
def self.read( path, width: TILE_WIDTH, height: TILE_WIDTH ) ## convenience helper
|
11
|
+
img = ChunkyPNG::Image.from_file( path )
|
12
|
+
new( img, width: width,
|
13
|
+
height: width )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
10
17
|
def initialize( *args, **kwargs )
|
11
18
|
@tile_width = kwargs[:width] || kwargs[:tile_width] || TILE_WIDTH
|
12
19
|
@tile_height = kwargs[:height] || kwargs[:tile_height] || TILE_HEIGHT
|
13
20
|
|
21
|
+
## check for background
|
22
|
+
background = kwargs[:background] || kwargs[:tile_background]
|
23
|
+
|
24
|
+
if background
|
25
|
+
## wrap into an array if not already an array
|
26
|
+
## and convert all colors to true rgba colors as integer numbers
|
27
|
+
background = [background] unless background.is_a?( Array )
|
28
|
+
@background_colors = background.map { |color| Color.parse( color ) }
|
29
|
+
else
|
30
|
+
## todo/check: use empty array instead of nil - why? why not?
|
31
|
+
@background_colors = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
|
14
35
|
## todo/fix: check type - args[0] is Image!!!
|
15
36
|
if args.size == 1 ## assume "copy" c'tor with passed in image
|
16
37
|
img = args[0] ## pass image through as-is
|
@@ -25,12 +46,25 @@ class ImageComposite < Image # check: (re)name to Collage, Sheet, Sprites, or s
|
|
25
46
|
@tile_rows = args[1] || 3
|
26
47
|
@tile_count = 0 # (track) current index (of added images)
|
27
48
|
|
28
|
-
|
29
|
-
|
49
|
+
background_color = if @background_colors && @background_colors.size == 1
|
50
|
+
@background_colors[0]
|
51
|
+
else
|
52
|
+
0 # note: 0 is transparent (0) true color
|
53
|
+
end
|
54
|
+
|
55
|
+
## todo/check - always auto-fill complete image (even if empty/no tiles)
|
56
|
+
## with background color if only one background color
|
57
|
+
## why? why not??? or always follow the "model"
|
58
|
+
## with more than one background color???
|
59
|
+
img = ChunkyPNG::Image.new( @tile_cols * @tile_width,
|
60
|
+
@tile_rows * @tile_height,
|
61
|
+
background_color )
|
62
|
+
|
30
63
|
else
|
31
64
|
raise ArgumentError, "cols, rows or image arguments expected; got: #{args.inspect}"
|
32
65
|
end
|
33
66
|
|
67
|
+
|
34
68
|
puts " #{img.height}x#{img.width} (height x width)"
|
35
69
|
|
36
70
|
super( nil, nil, img )
|
@@ -39,19 +73,46 @@ class ImageComposite < Image # check: (re)name to Collage, Sheet, Sprites, or s
|
|
39
73
|
|
40
74
|
def count() @tile_count; end
|
41
75
|
alias_method :size, :count ## add size alias (confusing if starting with 0?) - why? why not?
|
76
|
+
alias_method :tile_count, :count
|
77
|
+
|
78
|
+
def tile_width() @tile_width; end
|
79
|
+
def tile_height() @tile_height; end
|
80
|
+
|
81
|
+
|
42
82
|
|
43
83
|
#####
|
44
84
|
# set / add tile
|
45
|
-
|
46
|
-
def add( image )
|
85
|
+
def _add( image )
|
47
86
|
y, x = @tile_count.divmod( @tile_cols )
|
48
87
|
|
49
88
|
puts " [#{@tile_count}] @ (#{x}/#{y}) #{image.width}x#{image.height} (height x width)"
|
50
89
|
|
90
|
+
## note: only used if more than one background color specified
|
91
|
+
## needs to cycle through
|
92
|
+
if @background_colors && @background_colors.size > 1
|
93
|
+
i = x + y*@tile_cols
|
94
|
+
|
95
|
+
## note: cycle through background color for now
|
96
|
+
background_color = @background_colors[i % @background_colors.size]
|
97
|
+
background = Image.new( @tile_width, @tile_height, background_color ) ## todo/chekc: use "raw" ChunkyPNG:Image here - why? why not?
|
98
|
+
background.compose!( image )
|
99
|
+
image = background ## switch - make image with background new image
|
100
|
+
end
|
101
|
+
|
51
102
|
## note: image.image - "unwrap" the "raw" ChunkyPNG::Image
|
52
103
|
@img.compose!( image.image, x*@tile_width, y*@tile_height )
|
53
104
|
@tile_count += 1
|
54
105
|
end
|
106
|
+
|
107
|
+
def add( image_or_images ) ## note: allow adding of image OR array of images
|
108
|
+
if image_or_images.is_a?( Array )
|
109
|
+
images = image_or_images
|
110
|
+
images.each { |image| _add( image ) }
|
111
|
+
else
|
112
|
+
image = image_or_images
|
113
|
+
_add( image )
|
114
|
+
end
|
115
|
+
end
|
55
116
|
alias_method :<<, :add
|
56
117
|
|
57
118
|
|
@@ -73,5 +134,21 @@ class ImageComposite < Image # check: (re)name to Collage, Sheet, Sprites, or s
|
|
73
134
|
super ## e.g [x,y] --- get pixel
|
74
135
|
end
|
75
136
|
end
|
137
|
+
|
138
|
+
|
139
|
+
## convenience helpers to loop over composite
|
140
|
+
def each( &block )
|
141
|
+
count.times do |i|
|
142
|
+
block.call( tile( i ) )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def each_with_index( &block )
|
147
|
+
count.times do |i|
|
148
|
+
block.call( tile( i ), i )
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
76
153
|
end # class ImageComposite
|
77
154
|
end # module Pixelart
|
data/lib/pixelart/image.rb
CHANGED
@@ -64,18 +64,21 @@ end
|
|
64
64
|
|
65
65
|
|
66
66
|
|
67
|
-
def zoom( zoom=2 )
|
67
|
+
def zoom( zoom=2, spacing: 0 )
|
68
68
|
## create a new zoom factor x image (2x, 3x, etc.)
|
69
69
|
|
70
|
-
|
71
|
-
|
70
|
+
width = @img.width*zoom+(@img.width-1)*spacing
|
71
|
+
height = @img.height*zoom+(@img.height-1)*spacing
|
72
72
|
|
73
|
-
|
74
|
-
|
73
|
+
img = Image.new( width, height )
|
74
|
+
|
75
|
+
@img.width.times do |x|
|
76
|
+
@img.height.times do |y|
|
75
77
|
pixel = @img[x,y]
|
76
78
|
zoom.times do |n|
|
77
79
|
zoom.times do |m|
|
78
|
-
img[n+zoom*x
|
80
|
+
img[n+zoom*x+spacing*x,
|
81
|
+
m+zoom*y+spacing*y] = pixel
|
79
82
|
end
|
80
83
|
end
|
81
84
|
end # each x
|
@@ -86,6 +89,12 @@ end
|
|
86
89
|
alias_method :scale, :zoom
|
87
90
|
|
88
91
|
|
92
|
+
def crop( x, y, crop_width, crop_height )
|
93
|
+
Image.new( nil, nil,
|
94
|
+
image.crop( x,y, crop_width, crop_height ) )
|
95
|
+
end
|
96
|
+
|
97
|
+
|
89
98
|
|
90
99
|
#######################
|
91
100
|
## filter / effects
|
@@ -94,12 +103,25 @@ def grayscale
|
|
94
103
|
img = @img.grayscale
|
95
104
|
Image.new( img.width, img.height, img )
|
96
105
|
end
|
106
|
+
alias_method :greyscale, :grayscale
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def flip
|
111
|
+
img = @img.flip
|
112
|
+
Image.new( img.width, img.height, img )
|
113
|
+
end
|
114
|
+
alias_method :flip_horizontally, :flip
|
97
115
|
|
98
116
|
def mirror
|
99
117
|
img = @img.mirror
|
100
118
|
Image.new( img.width, img.height, img )
|
101
119
|
end
|
102
120
|
alias_method :flip_vertically, :mirror
|
121
|
+
alias_method :flop, :mirror
|
122
|
+
|
123
|
+
|
124
|
+
|
103
125
|
|
104
126
|
|
105
127
|
## add replace_colors alias too? - why? why not?
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
|
4
|
+
## todo/check:
|
5
|
+
## use a different name for silhouette
|
6
|
+
## - why not - outline ???
|
7
|
+
## or - shadow ???
|
8
|
+
## or - profile ???
|
9
|
+
## or - figure ???
|
10
|
+
## or - shape ???
|
11
|
+
## or - form ???
|
12
|
+
|
13
|
+
class Image
|
14
|
+
def silhouette( color='#000000' )
|
15
|
+
color = Color.parse( color )
|
16
|
+
|
17
|
+
img = Image.new( @img.width, @img.height )
|
18
|
+
|
19
|
+
@img.width.times do |x|
|
20
|
+
@img.height.times do |y|
|
21
|
+
pixel = @img[x,y]
|
22
|
+
|
23
|
+
img[x,y] = if pixel == Color::TRANSPARENT # transparent (0)
|
24
|
+
Color::TRANSPARENT
|
25
|
+
else
|
26
|
+
color
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
img
|
31
|
+
end
|
32
|
+
|
33
|
+
end # class Image
|
34
|
+
|
35
|
+
end # module Pixelart
|
data/lib/pixelart/spots.rb
CHANGED
@@ -3,41 +3,67 @@ module Pixelart
|
|
3
3
|
class Image
|
4
4
|
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
def spots_hidef( spot=10,
|
7
|
+
spacing: 0,
|
8
|
+
center: nil,
|
9
|
+
radius: nil,
|
10
|
+
background: nil,
|
11
|
+
lightness: nil,
|
12
|
+
odd: false )
|
13
|
+
|
14
|
+
width = @img.width*spot+(@img.width-1)*spacing
|
15
|
+
height = @img.height*spot+(@img.height-1)*spacing
|
13
16
|
|
14
|
-
width = @img.width
|
15
|
-
height = @img.height
|
16
17
|
## puts " #{width}x#{height}"
|
17
18
|
|
19
|
+
## settings in a hash for "pretty printing" in comments
|
20
|
+
settings = { spot: spot
|
21
|
+
}
|
22
|
+
|
23
|
+
settings[ :spacing ] = spacing if spacing
|
24
|
+
settings[ :center ] = center if center
|
25
|
+
settings[ :radius ] = radius if radius
|
26
|
+
settings[ :background ] = background if background
|
27
|
+
settings[ :lightness ] = lightness if lightness
|
28
|
+
settings[ :odd ] = odd if odd
|
29
|
+
|
30
|
+
|
31
|
+
v = Vector.new( width, height, header: <<TXT )
|
32
|
+
generated by pixelart/v#{VERSION} on #{Time.now.utc}
|
33
|
+
|
34
|
+
spots_hidef with settings:
|
35
|
+
#{settings.to_json}
|
36
|
+
TXT
|
37
|
+
|
38
|
+
|
18
39
|
min_center, max_center = center ? center : [0,0]
|
19
40
|
min_radius, max_radius = radius ? radius : [0,0]
|
20
41
|
|
21
|
-
|
42
|
+
## note: allow passing in array of colors (will get randomally picked)
|
43
|
+
background_colors = if background
|
44
|
+
## check for array; otherwise assume single color passed in
|
45
|
+
background_ary = background.is_a?( Array) ? background : [background]
|
46
|
+
background_ary.map { |background| Color.parse( background ) }
|
47
|
+
else
|
48
|
+
[0] # transparent (default - no background)
|
49
|
+
end
|
22
50
|
|
23
|
-
min_lightness, max_lightness = lightness ? lightness : [0.0,0.0]
|
24
51
|
|
25
|
-
|
26
|
-
## save commands to a script
|
27
|
-
script = String.new('')
|
28
|
-
script << "#{MAGICK_INPUT} \\\n"
|
52
|
+
min_lightness, max_lightness = lightness ? lightness : [0.0,0.0]
|
29
53
|
|
30
54
|
|
31
|
-
|
32
|
-
height.times do |y|
|
55
|
+
@img.width.times do |x|
|
56
|
+
@img.height.times do |y|
|
33
57
|
color = @img[ x, y ]
|
34
58
|
|
35
|
-
if color == 0
|
36
|
-
if background
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
59
|
+
if color == 0 ## transparent
|
60
|
+
next if background.nil?
|
61
|
+
|
62
|
+
color = if background_colors.size == 1
|
63
|
+
background_colors[0]
|
64
|
+
else ## pick random background color
|
65
|
+
background_colors[ rand( background_colors.size ) ]
|
66
|
+
end
|
41
67
|
end
|
42
68
|
|
43
69
|
|
@@ -65,7 +91,6 @@ def spots( spot=10,
|
|
65
91
|
# e.g. 0 becomes #00000000
|
66
92
|
# and so on
|
67
93
|
color_hex = Color.to_hex( color, include_alpha: true )
|
68
|
-
script << "-fill '#{color_hex}' "
|
69
94
|
|
70
95
|
cx_offset,
|
71
96
|
cy_offset = if center ## randomize (offset off center +/-)
|
@@ -79,48 +104,40 @@ def spots( spot=10,
|
|
79
104
|
cx = x*spot + x*spacing + cx_offset
|
80
105
|
cy = y*spot + y*spacing + cx_offset
|
81
106
|
|
82
|
-
|
107
|
+
r = if radius ## randomize (radius +/-)
|
83
108
|
min_radius + rand( max_radius-min_radius )
|
84
109
|
else
|
85
110
|
spot/2
|
86
111
|
end
|
87
|
-
px = cx + px_offset
|
88
|
-
py = cy
|
89
112
|
|
113
|
+
cx += spot/2 if odd && (y % 2 == 1) ## add odd offset
|
90
114
|
|
91
|
-
if odd && (y % 2 == 1) ## add odd offset
|
92
|
-
cx += spot/2
|
93
|
-
px += spot/2
|
94
|
-
end
|
95
115
|
|
96
|
-
|
97
|
-
## give the center and any point on the perimeter (boundary)
|
98
|
-
script << "-draw 'circle #{cx},#{cy},#{px},#{py}' \\\n"
|
116
|
+
v.circle( cx: cx, cy: cy, r: r, fill: color_hex)
|
99
117
|
end
|
100
118
|
end
|
119
|
+
v
|
120
|
+
end ## method spots_hidef
|
121
|
+
alias_method :spots_hd, :spots_hidef
|
101
122
|
|
102
|
-
script << "-write #{MAGICK_OUTPUT}\n"
|
103
|
-
|
104
|
-
|
105
|
-
## use an empty image (canvas) with transparent background
|
106
|
-
## as magick input image
|
107
|
-
canvas = Image.new( width*spot+(width-1)*spacing,
|
108
|
-
height*spot+(height-1)*spacing )
|
109
|
-
canvas.save( MAGICK_INPUT )
|
110
|
-
|
111
|
-
## note: save magick input first (see above) before save script
|
112
|
-
## will (auto-)create missing directories in path (if missing)
|
113
|
-
|
114
|
-
File.open( MAGICK_SCRIPT, 'w:utf-8' ) do |f|
|
115
|
-
f.write( script )
|
116
|
-
end
|
117
123
|
|
124
|
+
def spots( spot=10,
|
125
|
+
spacing: 0,
|
126
|
+
center: nil,
|
127
|
+
radius: nil,
|
128
|
+
background: nil,
|
129
|
+
lightness: nil,
|
130
|
+
odd: false )
|
118
131
|
|
119
|
-
|
120
|
-
|
121
|
-
|
132
|
+
v = spots_hidef( spot,
|
133
|
+
spacing: spacing,
|
134
|
+
center: center,
|
135
|
+
radius: radius,
|
136
|
+
background: background,
|
137
|
+
lightness: lightness,
|
138
|
+
odd: odd )
|
122
139
|
|
123
|
-
|
140
|
+
v.to_image
|
124
141
|
end
|
125
142
|
|
126
143
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
module Pixelart
|
3
|
+
class Image
|
4
|
+
|
5
|
+
def transparent( style = :solid, fuzzy: false )
|
6
|
+
img = Image.new( width, height )
|
7
|
+
|
8
|
+
|
9
|
+
background = self[0,0]
|
10
|
+
|
11
|
+
bh,bs,bl = Color.to_hsl( background )
|
12
|
+
bh = (bh % 360) ## might might negative degree (always make positive)
|
13
|
+
|
14
|
+
height.times do |y|
|
15
|
+
if style == :linear
|
16
|
+
background = self[0,y]
|
17
|
+
|
18
|
+
bh,bs,bl = Color.to_hsl( background )
|
19
|
+
bh = (bh % 360) ## might might negative degree (always make positive)
|
20
|
+
end
|
21
|
+
width.times do |x|
|
22
|
+
pixel = self[x,y]
|
23
|
+
|
24
|
+
if background == 0 ## special case if background is already transparent keep going
|
25
|
+
img[x,y] = pixel
|
26
|
+
elsif fuzzy
|
27
|
+
## check for more transparents
|
28
|
+
## not s is 0.0 to 0.99 (100%)
|
29
|
+
## and l is 0.0 to 0.99 (100%)
|
30
|
+
h,s,l = Color.to_hsl( pixel )
|
31
|
+
h = (h % 360) ## might might negative degree (always make positive)
|
32
|
+
|
33
|
+
## try some kind-of fuzzy "heuristic" match on background color
|
34
|
+
if ((h >= bh-5) && (h <= bh+5)) &&
|
35
|
+
((s >= bs-0.07) && (s <= bs+0.07)) &&
|
36
|
+
((l >= bl-0.07) && (l <= bl+0.07))
|
37
|
+
img[x,y] = 0 ## Color::TRANSPARENT
|
38
|
+
|
39
|
+
if h != bh || s != bs || l != bl
|
40
|
+
# report fuzzy background color
|
41
|
+
puts " #{x}/#{y} fuzzy background #{[h,s,l]} ~= #{[bh,bs,bl]}"
|
42
|
+
end
|
43
|
+
else
|
44
|
+
img[x,y] = pixel
|
45
|
+
end
|
46
|
+
else
|
47
|
+
if pixel == background
|
48
|
+
img[x,y] = 0 ## Color::TRANSPARENT
|
49
|
+
else
|
50
|
+
img[x,y] = pixel
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
img
|
56
|
+
end # method transparent
|
57
|
+
|
58
|
+
|
59
|
+
end # class Image
|
60
|
+
end # module Pixelart
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
|
4
|
+
class Vector # holds a vector graphics image (in source)
|
5
|
+
|
6
|
+
class Shape; end
|
7
|
+
class Circle < Shape
|
8
|
+
def initialize( cx, cy, r, fill: )
|
9
|
+
@cx = cx
|
10
|
+
@cy = cy
|
11
|
+
@r = r
|
12
|
+
@fill = fill
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_svg
|
16
|
+
%Q{<circle cx="#{@cx}" cy="#{@cy}" r="#{@r}" fill="#{@fill}" />}
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_magick
|
20
|
+
## circle
|
21
|
+
## give the center and any point on the perimeter (boundary)
|
22
|
+
px = @cx+@r
|
23
|
+
py = @cy
|
24
|
+
"-fill '#{@fill}' -draw 'circle #{@cx},#{@cy},#{px},#{py}'"
|
25
|
+
end
|
26
|
+
end # class Circle
|
27
|
+
|
28
|
+
class Path < Shape
|
29
|
+
def initialize( fill:, stroke: )
|
30
|
+
@commands = []
|
31
|
+
@fill = fill
|
32
|
+
@stroke = stroke
|
33
|
+
end
|
34
|
+
|
35
|
+
def move_to( x, y ) ## add a move_to instruction
|
36
|
+
@commands << ['M', x, y]
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def line_to( x, y )
|
41
|
+
@commands << ['L', x, y]
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def line( *coords ) ## move_to/line_to all-in-one shortcut
|
46
|
+
## todo/check - assert coords has at least two x/y pairs - why? why not?
|
47
|
+
move_to( *coords[0..1] )
|
48
|
+
coords[2..-1].each_slice( 2) do |coord|
|
49
|
+
line_to( *coord )
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
def to_svg
|
57
|
+
buf = %Q{<path style="stroke: #{@stroke}; fill: #{@fill || 'none'};" }
|
58
|
+
buf << %Q{d="}
|
59
|
+
last_command = ''
|
60
|
+
@commands.each_with_index do |command,i|
|
61
|
+
buf << " " if i > 0 # add space separator
|
62
|
+
|
63
|
+
## "optimize" - that is, do not repead command if same as before
|
64
|
+
buf << command[0] if command[0] != last_command
|
65
|
+
buf << "#{command[1]} #{command[2]}"
|
66
|
+
last_command = command[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
buf << %Q{"}
|
70
|
+
buf << "/>"
|
71
|
+
buf
|
72
|
+
end
|
73
|
+
end # class Path
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
def initialize( width, height, header: nil )
|
78
|
+
@width = width
|
79
|
+
@height = height
|
80
|
+
|
81
|
+
@header = header
|
82
|
+
@shapes = []
|
83
|
+
end
|
84
|
+
|
85
|
+
def circle( cx:, cy:, r:, fill: )
|
86
|
+
@shapes << Circle.new( cx, cy, r, fill: fill )
|
87
|
+
end
|
88
|
+
|
89
|
+
## note: default stroke (color) to black (#000000) for now - why? why not?
|
90
|
+
def path( stroke: '#000000', fill: nil )
|
91
|
+
path = Path.new( stroke: stroke, fill: fill )
|
92
|
+
@shapes << path
|
93
|
+
|
94
|
+
path ## note: MUST return "inner" path shape for chaining "dsl-like" methods / commands
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
def to_image
|
100
|
+
## use an empty image (canvas) with transparent background
|
101
|
+
## as magick input image
|
102
|
+
canvas = Image.new( @width, @height )
|
103
|
+
canvas.save( MAGICK_INPUT )
|
104
|
+
|
105
|
+
## note: magick command line might get way too long, thus,
|
106
|
+
## save commands to a script
|
107
|
+
## note: save magick input first (see above) before save script
|
108
|
+
## will (auto-)create missing directories in path (if missing)
|
109
|
+
|
110
|
+
File.open( MAGICK_SCRIPT, 'w:utf-8' ) do |f|
|
111
|
+
f.write( "#{MAGICK_INPUT} \\\n" )
|
112
|
+
@shapes.each do |shape|
|
113
|
+
f.write( "#{shape.to_magick} \\\n" )
|
114
|
+
end
|
115
|
+
f.write( "-write #{MAGICK_OUTPUT}\n" )
|
116
|
+
end
|
117
|
+
|
118
|
+
MiniMagick::Tool::Magick.new do |magick|
|
119
|
+
magick.script( MAGICK_SCRIPT )
|
120
|
+
end
|
121
|
+
|
122
|
+
Image.read( MAGICK_OUTPUT )
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def to_svg
|
127
|
+
buf = String.new('')
|
128
|
+
|
129
|
+
if @header
|
130
|
+
buf << "<!--\n"
|
131
|
+
## auto-indent lines by five (5) spaces for now
|
132
|
+
@header.each_line do |line|
|
133
|
+
buf << " #{line}"
|
134
|
+
end
|
135
|
+
buf << "\n-->\n\n"
|
136
|
+
end
|
137
|
+
|
138
|
+
buf << %Q{<svg version="1.1" width="#{@width}" height="#{@height}" xmlns="http://www.w3.org/2000/svg">\n}
|
139
|
+
@shapes.each do |shape|
|
140
|
+
buf << " #{shape.to_svg}\n"
|
141
|
+
end
|
142
|
+
buf << "</svg>"
|
143
|
+
buf
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def save( path, format: nil )
|
148
|
+
if format && format.downcase == 'png' ## support png with image magick
|
149
|
+
img = to_image
|
150
|
+
img.save( path )
|
151
|
+
else
|
152
|
+
# make sure outdir exits
|
153
|
+
outdir = File.dirname( path )
|
154
|
+
FileUtils.mkdir_p( outdir ) unless Dir.exist?( outdir )
|
155
|
+
File.open( path, 'w:utf-8' ) do |f|
|
156
|
+
f.write( to_svg )
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
alias_method :write, :save
|
161
|
+
|
162
|
+
end # class Vector
|
163
|
+
end # module Pixelart
|
data/lib/pixelart/version.rb
CHANGED
data/lib/pixelart.rb
CHANGED
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: 1.
|
4
|
+
version: 1.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:
|
11
|
+
date: 2022-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chunky_png
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: csvreader
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rdoc
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,10 +111,13 @@ files:
|
|
97
111
|
- lib/pixelart/misc.rb
|
98
112
|
- lib/pixelart/palette.rb
|
99
113
|
- lib/pixelart/pixelator.rb
|
114
|
+
- lib/pixelart/silhouette.rb
|
100
115
|
- lib/pixelart/sketch.rb
|
101
116
|
- lib/pixelart/spots.rb
|
117
|
+
- lib/pixelart/transparent.rb
|
118
|
+
- lib/pixelart/vector.rb
|
102
119
|
- lib/pixelart/version.rb
|
103
|
-
homepage: https://github.com/
|
120
|
+
homepage: https://github.com/pixelartexchange/pixel
|
104
121
|
licenses:
|
105
122
|
- Public Domain
|
106
123
|
metadata: {}
|