pixelart 1.2.3 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -1,33 +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
- def ukraine
12
- ### move colors to (reusable) constants e.g.
13
- ### UKRAINE_BLUE
14
- ### UKRAINE_YELLOW - why? why not?
15
- ukraine_blue = Color.parse( '#0057b7' )
16
- ukraine_yellow = Color.parse( '#ffdd00' )
17
-
18
- img = Image.new( @img.width, @img.height )
19
-
20
- @img.height.times do |y|
21
- color = (y < @img.height/2) ? ukraine_blue : ukraine_yellow
22
- @img.width.times do |x|
23
- img[x,y] = color
24
- end
25
- end
26
-
27
- img.compose!( self ) ## paste/compose image onto backgorund
28
- img
29
- end
30
- end # class Image
31
-
32
- end # module Pixelart
33
-
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
+
@@ -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
@@ -1,22 +1,22 @@
1
-
2
- module Pixelart
3
-
4
- MAJOR = 1
5
- MINOR = 2
6
- PATCH = 3
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 = 2
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
+