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.
@@ -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
@@ -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
+
@@ -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 = 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
-
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
+