pixelart 1.2.2 → 1.3.1
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/CHANGELOG.md +3 -3
- data/Manifest.txt +5 -0
- data/README.md +290 -290
- data/Rakefile +32 -32
- data/lib/pixelart/base.rb +93 -84
- data/lib/pixelart/blur.rb +19 -19
- data/lib/pixelart/circle.rb +46 -0
- data/lib/pixelart/color.rb +131 -131
- data/lib/pixelart/composite.rb +154 -154
- data/lib/pixelart/generator.rb +199 -0
- data/lib/pixelart/gradient.rb +106 -106
- data/lib/pixelart/image.rb +283 -272
- data/lib/pixelart/led.rb +37 -37
- data/lib/pixelart/misc.rb +66 -66
- data/lib/pixelart/palette.rb +72 -72
- data/lib/pixelart/pixelator.rb +165 -165
- data/lib/pixelart/sample.rb +120 -0
- data/lib/pixelart/silhouette.rb +35 -35
- data/lib/pixelart/sketch.rb +69 -69
- data/lib/pixelart/spots.rb +146 -146
- data/lib/pixelart/stripes.rb +116 -0
- data/lib/pixelart/transparent.rb +60 -60
- data/lib/pixelart/ukraine.rb +20 -0
- data/lib/pixelart/vector.rb +163 -163
- data/lib/pixelart/version.rb +22 -22
- data/lib/pixelart.rb +12 -12
- metadata +11 -6
@@ -0,0 +1,116 @@
|
|
1
|
+
module Pixelart
|
2
|
+
class Image
|
3
|
+
|
4
|
+
|
5
|
+
def self.calc_stripes( length, n: 2, debug: false )
|
6
|
+
stripes = []
|
7
|
+
|
8
|
+
base_step = length / n ## pixels per pixel
|
9
|
+
|
10
|
+
err_step = (length % n) * 2 ## multiply by 2
|
11
|
+
denominator = n * 2 # denominator (in de - nenner e.g. 1/nenner 4/nenner)
|
12
|
+
|
13
|
+
overflow = err_step*n/denominator ## todo/check - assert that div is always WITHOUT remainder!!!!!
|
14
|
+
|
15
|
+
if debug
|
16
|
+
puts
|
17
|
+
puts "base_step (pixels per stripe):"
|
18
|
+
puts " #{base_step} - #{base_step}px * #{n} = #{base_step*n}px"
|
19
|
+
puts "err_step (in 1/#{length}*2):"
|
20
|
+
puts " #{err_step} / #{denominator} - #{err_step*n} / #{denominator} = +#{err_step*n/denominator}px overflow"
|
21
|
+
puts
|
22
|
+
end
|
23
|
+
|
24
|
+
err = 0
|
25
|
+
stripe = 0
|
26
|
+
|
27
|
+
n.times do |i|
|
28
|
+
stripe = base_step
|
29
|
+
err += err_step
|
30
|
+
|
31
|
+
if err >= denominator ## overflow
|
32
|
+
puts " -- overflow #{err}/#{denominator} - add +1 pixel to stripe #{i}" if debug
|
33
|
+
|
34
|
+
stripe += 1
|
35
|
+
err -= denominator
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
puts " #{i} => #{stripe} -- #{err} / #{denominator}" if debug
|
40
|
+
|
41
|
+
stripes[i] = stripe
|
42
|
+
end
|
43
|
+
|
44
|
+
## note: assert calculation - sum of stripes MUST be equal length
|
45
|
+
sum = stripes.sum
|
46
|
+
puts " sum: #{sum}" if debug
|
47
|
+
|
48
|
+
if sum != length
|
49
|
+
puts "!! ERROR - stripes sum #{sum} calculation failed; expected #{length}:"
|
50
|
+
pp stripes
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
stripes
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def stripes_horizontal( *colors )
|
59
|
+
colors = colors.map { |color| Color.parse( color ) }
|
60
|
+
|
61
|
+
img = Image.new( @img.width, @img.height )
|
62
|
+
|
63
|
+
n = colors.size
|
64
|
+
lengths = self.class.calc_stripes( @img.height, n: n )
|
65
|
+
|
66
|
+
i = 0
|
67
|
+
length = lengths[0]
|
68
|
+
color = colors[0]
|
69
|
+
|
70
|
+
@img.height.times do |y|
|
71
|
+
if y >= length
|
72
|
+
i += 1
|
73
|
+
length += lengths[i]
|
74
|
+
color = colors[i]
|
75
|
+
end
|
76
|
+
@img.width.times do |x|
|
77
|
+
img[x,y] = color
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
img.compose!( self ) ## paste/compose image onto backgorund
|
82
|
+
img
|
83
|
+
end
|
84
|
+
alias_method :stripes, :stripes_horizontal
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
### todo/check: move colors to (reusable) constants int Color !!!! why? why not?
|
89
|
+
RAINBOW_RED = Color.parse( '#E40303' )
|
90
|
+
RAINBOW_ORANGE = Color.parse( '#FF8C00' )
|
91
|
+
RAINBOW_YELLOW = Color.parse( '#FFED00' )
|
92
|
+
RAINBOW_GREEN = Color.parse( '#008026' )
|
93
|
+
RAINBOW_BLUE = Color.parse( '#004DFF' )
|
94
|
+
RAINBOW_VIOLET = Color.parse( '#750787' )
|
95
|
+
|
96
|
+
|
97
|
+
def rainbow
|
98
|
+
##
|
99
|
+
# the most common variant consists of six stripes:
|
100
|
+
# red, orange, yellow, green, blue, and violet.
|
101
|
+
# The flag is typically flown horizontally,
|
102
|
+
# with the red stripe on top, as it would be in a natural rainbow
|
103
|
+
#
|
104
|
+
# see https://en.wikipedia.org/wiki/Rainbow_flag_(LGBT)
|
105
|
+
stripes( RAINBOW_RED,
|
106
|
+
RAINBOW_ORANGE,
|
107
|
+
RAINBOW_YELLOW,
|
108
|
+
RAINBOW_GREEN,
|
109
|
+
RAINBOW_BLUE,
|
110
|
+
RAINBOW_VIOLET )
|
111
|
+
end
|
112
|
+
alias_method :pride, :rainbow
|
113
|
+
|
114
|
+
|
115
|
+
end # class Image
|
116
|
+
end # module Pixelart
|
data/lib/pixelart/transparent.rb
CHANGED
@@ -1,60 +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
|
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,20 @@
|
|
1
|
+
#######
|
2
|
+
#
|
3
|
+
# Glory To Ukraine! Fuck (Vladimir) Putin! Stop the War!
|
4
|
+
# Send A Stop The War Message To The World With Your Pixel Art Images
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Pixelart
|
9
|
+
|
10
|
+
class Image
|
11
|
+
|
12
|
+
### todo/check: move colors to (reusable) constants int Color !!!! why? why not?
|
13
|
+
UKRAINE_BLUE = Color.parse( '#0057b7' )
|
14
|
+
UKRAINE_YELLOW = Color.parse( '#ffdd00' )
|
15
|
+
|
16
|
+
def ukraine() stripes( UKRAINE_BLUE, UKRAINE_YELLOW ); end
|
17
|
+
end # class Image
|
18
|
+
|
19
|
+
end # module Pixelart
|
20
|
+
|
data/lib/pixelart/vector.rb
CHANGED
@@ -1,163 +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
|
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
@@ -1,22 +1,22 @@
|
|
1
|
-
|
2
|
-
module Pixelart
|
3
|
-
|
4
|
-
MAJOR = 1
|
5
|
-
MINOR =
|
6
|
-
PATCH =
|
7
|
-
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
8
|
-
|
9
|
-
def self.version
|
10
|
-
VERSION
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.banner
|
14
|
-
"pixelart/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.root
|
18
|
-
File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
|
19
|
-
end
|
20
|
-
|
21
|
-
end # module Pixelart
|
22
|
-
|
1
|
+
|
2
|
+
module Pixelart
|
3
|
+
|
4
|
+
MAJOR = 1
|
5
|
+
MINOR = 3
|
6
|
+
PATCH = 1
|
7
|
+
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
8
|
+
|
9
|
+
def self.version
|
10
|
+
VERSION
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.banner
|
14
|
+
"pixelart/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.root
|
18
|
+
File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
|
19
|
+
end
|
20
|
+
|
21
|
+
end # module Pixelart
|
22
|
+
|
data/lib/pixelart.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
|
2
|
-
## our own code (without "top-level" shortcuts e.g. "modular version")
|
3
|
-
require 'pixelart/base' # aka "strict(er)" version
|
4
|
-
|
5
|
-
|
6
|
-
###
|
7
|
-
# add convenience top-level shortcuts / aliases
|
8
|
-
# make Image, Color, Palette8bit, etc top-level
|
9
|
-
include Pixelart
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
|
2
|
+
## our own code (without "top-level" shortcuts e.g. "modular version")
|
3
|
+
require 'pixelart/base' # aka "strict(er)" version
|
4
|
+
|
5
|
+
|
6
|
+
###
|
7
|
+
# add convenience top-level shortcuts / aliases
|
8
|
+
# make Image, Color, Palette8bit, etc top-level
|
9
|
+
include Pixelart
|
10
|
+
|
11
|
+
|
12
|
+
|