pixelart 1.2.3 → 1.3.0

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.
@@ -1,154 +1,154 @@
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 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
-
17
- def initialize( *args, **kwargs )
18
- @tile_width = kwargs[:width] || kwargs[:tile_width] || TILE_WIDTH
19
- @tile_height = kwargs[:height] || kwargs[:tile_height] || TILE_HEIGHT
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
-
35
- ## todo/fix: check type - args[0] is Image!!!
36
- if args.size == 1 ## assume "copy" c'tor with passed in image
37
- img = args[0] ## pass image through as-is
38
-
39
- @tile_cols = img.width / @tile_width ## e.g. 2400/24 = 100
40
- @tile_rows = img.height / @tile_height ## e.g. 2400/24 = 100
41
- @tile_count = @tile_cols * @tile_rows ## ## 10000 = 100x100 (2400x2400 pixel)
42
- elsif args.size == 2 || args.size == 0 ## cols, rows
43
- ## todo/fix: check type - args[0] & args[1] is Integer!!!!!
44
- ## todo/check - find a better name for cols/rows - why? why not?
45
- @tile_cols = args[0] || 3
46
- @tile_rows = args[1] || 3
47
- @tile_count = 0 # (track) current index (of added images)
48
-
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
-
63
- else
64
- raise ArgumentError, "cols, rows or image arguments expected; got: #{args.inspect}"
65
- end
66
-
67
-
68
- puts " #{img.height}x#{img.width} (height x width)"
69
-
70
- super( nil, nil, img )
71
- end
72
-
73
-
74
- def count() @tile_count; end
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
-
82
-
83
- #####
84
- # set / add tile
85
- def _add( image )
86
- y, x = @tile_count.divmod( @tile_cols )
87
-
88
- puts " [#{@tile_count}] @ (#{x}/#{y}) #{image.width}x#{image.height} (height x width)"
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
-
102
- ## note: image.image - "unwrap" the "raw" ChunkyPNG::Image
103
- @img.compose!( image.image, x*@tile_width, y*@tile_height )
104
- @tile_count += 1
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
116
- alias_method :<<, :add
117
-
118
-
119
-
120
- ######
121
- # get tile
122
-
123
- def tile( index )
124
- y, x = index.divmod( @tile_cols )
125
- img = @img.crop( x*@tile_width, y*@tile_height, @tile_width, @tile_height )
126
- Image.new( img.width, img.height, img ) ## wrap in pixelart image
127
- end
128
-
129
- def []( *args ) ## overload - why? why not?
130
- if args.size == 1
131
- index = args[0]
132
- tile( index )
133
- else
134
- super ## e.g [x,y] --- get pixel
135
- end
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
-
153
- end # class ImageComposite
154
- end # module Pixelart
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 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
+
17
+ def initialize( *args, **kwargs )
18
+ @tile_width = kwargs[:width] || kwargs[:tile_width] || TILE_WIDTH
19
+ @tile_height = kwargs[:height] || kwargs[:tile_height] || TILE_HEIGHT
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
+
35
+ ## todo/fix: check type - args[0] is Image!!!
36
+ if args.size == 1 ## assume "copy" c'tor with passed in image
37
+ img = args[0] ## pass image through as-is
38
+
39
+ @tile_cols = img.width / @tile_width ## e.g. 2400/24 = 100
40
+ @tile_rows = img.height / @tile_height ## e.g. 2400/24 = 100
41
+ @tile_count = @tile_cols * @tile_rows ## ## 10000 = 100x100 (2400x2400 pixel)
42
+ elsif args.size == 2 || args.size == 0 ## cols, rows
43
+ ## todo/fix: check type - args[0] & args[1] is Integer!!!!!
44
+ ## todo/check - find a better name for cols/rows - why? why not?
45
+ @tile_cols = args[0] || 3
46
+ @tile_rows = args[1] || 3
47
+ @tile_count = 0 # (track) current index (of added images)
48
+
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
+
63
+ else
64
+ raise ArgumentError, "cols, rows or image arguments expected; got: #{args.inspect}"
65
+ end
66
+
67
+
68
+ puts " #{img.height}x#{img.width} (height x width)"
69
+
70
+ super( nil, nil, img )
71
+ end
72
+
73
+
74
+ def count() @tile_count; end
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
+
82
+
83
+ #####
84
+ # set / add tile
85
+ def _add( image )
86
+ y, x = @tile_count.divmod( @tile_cols )
87
+
88
+ puts " [#{@tile_count}] @ (#{x}/#{y}) #{image.width}x#{image.height} (height x width)"
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
+
102
+ ## note: image.image - "unwrap" the "raw" ChunkyPNG::Image
103
+ @img.compose!( image.image, x*@tile_width, y*@tile_height )
104
+ @tile_count += 1
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
116
+ alias_method :<<, :add
117
+
118
+
119
+
120
+ ######
121
+ # get tile
122
+
123
+ def tile( index )
124
+ y, x = index.divmod( @tile_cols )
125
+ img = @img.crop( x*@tile_width, y*@tile_height, @tile_width, @tile_height )
126
+ Image.new( img.width, img.height, img ) ## wrap in pixelart image
127
+ end
128
+
129
+ def []( *args ) ## overload - why? why not?
130
+ if args.size == 1
131
+ index = args[0]
132
+ tile( index )
133
+ else
134
+ super ## e.g [x,y] --- get pixel
135
+ end
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
+
153
+ end # class ImageComposite
154
+ end # module Pixelart
@@ -1,106 +1,106 @@
1
-
2
- ## inspired / helped by
3
- ## https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
4
- ## https://github.com/mistic100/tinygradient
5
- ## https://mistic100.github.io/tinygradient/
6
- ## https://bsouthga.dev/posts/color-gradients-with-python
7
-
8
-
9
- module Pixelart
10
-
11
- class Gradient
12
-
13
- def initialize( *stops )
14
- ## note: convert stop colors to rgb triplets e.g.
15
- ## from #ffffff to [255,255,255]
16
- ## #000000 to [0,0,0] etc.
17
- @stops = stops.map do |stop|
18
- stop = Color.parse( stop )
19
- [Color.r(stop), Color.g(stop), Color.b(stop)]
20
- end
21
- end
22
-
23
-
24
- def colors( steps )
25
- segments = @stops.size - 1
26
-
27
- ## note: gradient will include start (first)
28
- ## AND stop color (last) - stop color is NOT excluded for now!!
29
- if segments == 1
30
- start = @stops[0]
31
- stop = @stops[1]
32
-
33
- gradient = linear_gradient( start, stop, steps,
34
- include_stop: true )
35
- else
36
- steps_per_segment, mod = steps.divmod( segments )
37
- raise ArgumentError, "steps (#{steps}) must be divisible by # of segments (#{segments}); expected mod of 0 but got #{mod}" if mod != 0
38
-
39
- gradient = []
40
- segments.times do |segment|
41
- start = @stops[segment]
42
- stop = @stops[segment+1]
43
- include_stop = (segment == segments-1) ## note: only include stop if last segment!!
44
-
45
- # print " segment #{segment+1}/#{segments} #{steps_per_segment} color(s) - "
46
- # print " start: #{start.inspect} "
47
- # print include_stop ? 'include' : 'exclude'
48
- # print " stop: #{stop.inspect}"
49
- # print "\n"
50
-
51
- gradient += linear_gradient( start, stop, steps_per_segment,
52
- include_stop: include_stop )
53
- end
54
- end
55
-
56
- ## convert to color (Integer)
57
- gradient.map do |color|
58
- Color.rgb( *color )
59
- end
60
- end
61
-
62
-
63
-
64
- def interpolate( start, stop, steps, n )
65
- ## note: n - expected to start with 1,2,3,etc.
66
- color = []
67
- 3.times do |i|
68
- stepize = Float(stop[i] - start[i]) / Float(steps-1)
69
- value = stepize * n
70
- ## convert back to Integer from Float
71
- ## add 0.5 for rounding up (starting with 0.5) - why? why not?
72
- value = (value+0.5).to_i
73
- value = start[i] + value
74
-
75
- color << value
76
- end
77
- color
78
- end
79
-
80
-
81
- def linear_gradient( start, stop, steps,
82
- include_stop: true )
83
-
84
- gradient = [start] ## auto-add start color (first color in gradient)
85
-
86
- if include_stop
87
- 1.upto( steps-2 ).each do |n| ## sub two (-2), that is, start and stop color
88
- gradient << interpolate( start, stop, steps, n )
89
- end
90
- # note: use original passed in stop color (should match calculated)
91
- gradient << stop
92
- else
93
- 1.upto( steps-1 ).each do |n| ## sub one (-1), that is, start color only
94
- ## note: add one (+1) to steps because stop color gets excluded (not included)!!
95
- gradient << interpolate( start, stop, steps+1, n )
96
- end
97
- end
98
-
99
- gradient
100
- end
101
-
102
-
103
-
104
- end # class Gradient
105
- end # module Pixelart
106
-
1
+
2
+ ## inspired / helped by
3
+ ## https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
4
+ ## https://github.com/mistic100/tinygradient
5
+ ## https://mistic100.github.io/tinygradient/
6
+ ## https://bsouthga.dev/posts/color-gradients-with-python
7
+
8
+
9
+ module Pixelart
10
+
11
+ class Gradient
12
+
13
+ def initialize( *stops )
14
+ ## note: convert stop colors to rgb triplets e.g.
15
+ ## from #ffffff to [255,255,255]
16
+ ## #000000 to [0,0,0] etc.
17
+ @stops = stops.map do |stop|
18
+ stop = Color.parse( stop )
19
+ [Color.r(stop), Color.g(stop), Color.b(stop)]
20
+ end
21
+ end
22
+
23
+
24
+ def colors( steps )
25
+ segments = @stops.size - 1
26
+
27
+ ## note: gradient will include start (first)
28
+ ## AND stop color (last) - stop color is NOT excluded for now!!
29
+ if segments == 1
30
+ start = @stops[0]
31
+ stop = @stops[1]
32
+
33
+ gradient = linear_gradient( start, stop, steps,
34
+ include_stop: true )
35
+ else
36
+ steps_per_segment, mod = steps.divmod( segments )
37
+ raise ArgumentError, "steps (#{steps}) must be divisible by # of segments (#{segments}); expected mod of 0 but got #{mod}" if mod != 0
38
+
39
+ gradient = []
40
+ segments.times do |segment|
41
+ start = @stops[segment]
42
+ stop = @stops[segment+1]
43
+ include_stop = (segment == segments-1) ## note: only include stop if last segment!!
44
+
45
+ # print " segment #{segment+1}/#{segments} #{steps_per_segment} color(s) - "
46
+ # print " start: #{start.inspect} "
47
+ # print include_stop ? 'include' : 'exclude'
48
+ # print " stop: #{stop.inspect}"
49
+ # print "\n"
50
+
51
+ gradient += linear_gradient( start, stop, steps_per_segment,
52
+ include_stop: include_stop )
53
+ end
54
+ end
55
+
56
+ ## convert to color (Integer)
57
+ gradient.map do |color|
58
+ Color.rgb( *color )
59
+ end
60
+ end
61
+
62
+
63
+
64
+ def interpolate( start, stop, steps, n )
65
+ ## note: n - expected to start with 1,2,3,etc.
66
+ color = []
67
+ 3.times do |i|
68
+ stepize = Float(stop[i] - start[i]) / Float(steps-1)
69
+ value = stepize * n
70
+ ## convert back to Integer from Float
71
+ ## add 0.5 for rounding up (starting with 0.5) - why? why not?
72
+ value = (value+0.5).to_i
73
+ value = start[i] + value
74
+
75
+ color << value
76
+ end
77
+ color
78
+ end
79
+
80
+
81
+ def linear_gradient( start, stop, steps,
82
+ include_stop: true )
83
+
84
+ gradient = [start] ## auto-add start color (first color in gradient)
85
+
86
+ if include_stop
87
+ 1.upto( steps-2 ).each do |n| ## sub two (-2), that is, start and stop color
88
+ gradient << interpolate( start, stop, steps, n )
89
+ end
90
+ # note: use original passed in stop color (should match calculated)
91
+ gradient << stop
92
+ else
93
+ 1.upto( steps-1 ).each do |n| ## sub one (-1), that is, start color only
94
+ ## note: add one (+1) to steps because stop color gets excluded (not included)!!
95
+ gradient << interpolate( start, stop, steps+1, n )
96
+ end
97
+ end
98
+
99
+ gradient
100
+ end
101
+
102
+
103
+
104
+ end # class Gradient
105
+ end # module Pixelart
106
+