pixelart 1.3.0 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f203be5e0f2e2a06f88711fd51da837fb71212e58c0bc9aef80358f661f10f58
4
- data.tar.gz: f1377fcab0ec03d91ca692ed8d6e2222bacf5aedc5e477121c4cf1950968ad22
3
+ metadata.gz: 0724e3e1f83da6ef84325d79706a2f59d773eb31478f1e4d797ff582dad64a4a
4
+ data.tar.gz: 7d75ebe3aef0abb3e3d142bf787995bbcdf797416912a387601110a3547d08fe
5
5
  SHA512:
6
- metadata.gz: b3c11e63d1764934b71a62410c23e14af07b42da9ce046a5853b92d493809abd3bede1ef3b40c025e61fd28d9efcb77f87bc31a9997def982dd65e67b62da0a0
7
- data.tar.gz: 78f8de9b81fa1ed6c6a77a5ea3bc6414aef36837314f5a2a073c1ab4683b31899c5bcdfe3be85ad7e3c5159f3ee708d9f231e97799f2262378f5b7b49998119a
6
+ metadata.gz: 6afe87fa92ec1de03bad71cd5f2ba298f78287b77f514a6c74cae6747c1e1d62790d722c07fb0a5d049dcf9966d4fa0a473688e71c717de534420f4746b711f2
7
+ data.tar.gz: 0b1a7cf13cdffa7410a6c0246f894d3ca5f1e3b1347fd90b9f7cbe9ed3cff4b4fc87c6704d80854b5337aa5f17e24631c28bc8188e4c6f86422e862d22949d43
data/Manifest.txt CHANGED
@@ -6,14 +6,13 @@ lib/pixelart.rb
6
6
  lib/pixelart/base.rb
7
7
  lib/pixelart/blur.rb
8
8
  lib/pixelart/circle.rb
9
- lib/pixelart/color.rb
10
9
  lib/pixelart/composite.rb
11
- lib/pixelart/gradient.rb
10
+ lib/pixelart/generator.rb
12
11
  lib/pixelart/image.rb
13
12
  lib/pixelart/led.rb
14
13
  lib/pixelart/misc.rb
15
- lib/pixelart/palette.rb
16
14
  lib/pixelart/pixelator.rb
15
+ lib/pixelart/sample.rb
17
16
  lib/pixelart/silhouette.rb
18
17
  lib/pixelart/sketch.rb
19
18
  lib/pixelart/spots.rb
data/README.md CHANGED
@@ -288,3 +288,9 @@ 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
+
292
+
293
+
294
+ ## Questions? Comments?
295
+
296
+ Post them on the [D.I.Y. Punk (Pixel) Art reddit](https://old.reddit.com/r/DIYPunkArt). Thanks.
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ Hoe.spec 'pixelart' do
18
18
  self.history_file = 'CHANGELOG.md'
19
19
 
20
20
  self.extra_deps = [
21
+ ['pixelart-colors'],
21
22
  ['chunky_png'],
22
23
  ['mini_magick'],
23
24
  ['csvreader'],
data/lib/pixelart/base.rb CHANGED
@@ -1,3 +1,9 @@
1
+ ###
2
+ # base module
3
+ require 'pixelart/colors'
4
+
5
+
6
+
1
7
  ###############
2
8
  # 3rd party
3
9
  require 'chunky_png'
@@ -11,32 +17,24 @@ require 'mini_magick'
11
17
  require 'csvreader'
12
18
 
13
19
 
14
- ## stdlib
15
- require 'pp'
16
- require 'time'
17
- require 'date'
18
- require 'fileutils'
19
-
20
- require 'json'
21
- require 'yaml'
22
-
23
-
24
-
25
20
 
26
21
  ## our own code
27
22
  require 'pixelart/version' # note: let version always go first
28
- require 'pixelart/color'
29
- require 'pixelart/gradient'
30
- require 'pixelart/palette'
31
23
  require 'pixelart/image'
32
24
  require 'pixelart/composite'
33
25
 
26
+ require 'pixelart/sample' ## (down)sample / pixelate
27
+
34
28
 
35
29
  require 'pixelart/pixelator'
36
30
 
37
31
  require 'pixelart/misc' ## misc helpers
38
32
  require 'pixelart/stripes'
39
33
 
34
+
35
+ require 'pixelart/generator' ## generate images from text via spritesheets
36
+
37
+
40
38
  #########################
41
39
  # (special) effects / filters / etc
42
40
  require 'pixelart/circle'
@@ -67,21 +65,4 @@ require 'pixelart/blur'
67
65
 
68
66
 
69
67
 
70
-
71
-
72
- ##########
73
- # add some spelling convenience variants
74
- PixelArt = Pixelart
75
-
76
- module Pixelart
77
- Palette256 = Palette8Bit = Palette8bit
78
-
79
- Palette256Image = Palette8BitImage = Palette8bitImage =
80
- ImagePalette256 = ImagePalette8Bit = ImagePalette8bit
81
-
82
- CompositeImage = ImageComposite
83
- end
84
-
85
-
86
-
87
68
  puts Pixelart.banner # say hello
@@ -0,0 +1,202 @@
1
+ ####
2
+ # "simple" generator (no different sizes, genders, etc.)
3
+ # uses built-in spritesheet for (archetypes &) attributes
4
+
5
+
6
+ module Pixelart
7
+
8
+ class Metadata
9
+ class Sprite
10
+ attr_reader :id, :name, :type, :more_names
11
+
12
+ def initialize( id:,
13
+ name:,
14
+ type:,
15
+ more_names: [] )
16
+ @id = id # zero-based index eg. 0,1,2,3, etc.
17
+ @name = name
18
+ @type = type
19
+ @more_names = more_names
20
+ end
21
+ end # class Metadata::Sprite
22
+ end # class Metadata
23
+
24
+
25
+
26
+
27
+ class Generator
28
+
29
+ ######
30
+ # static helpers - (turn into "true" static self.class methods - why? why not?)
31
+ #
32
+ def self.normalize_key( str )
33
+ ## add & e.g. B&W
34
+ ## add ' e.g. McDonald's Red
35
+ str.downcase.gsub(/[ ()&°'_-]/, '').strip
36
+ end
37
+
38
+ def self.normalize_name( str )
39
+ ## normalize spaces in more names
40
+ str.strip.gsub( /[ ]{2,}/, ' ' )
41
+ end
42
+
43
+ def normalize_key( str ) self.class.normalize_key( str ); end
44
+ def normalize_name( str ) self.class.normalize_name( str ); end
45
+
46
+
47
+
48
+ def build_attributes_by_name( recs )
49
+ h = {}
50
+ recs.each_with_index do |rec|
51
+ names = [rec.name] + rec.more_names
52
+
53
+ names.each do |name|
54
+ key = normalize_key( name )
55
+
56
+ if h[ key ]
57
+ puts "!!! ERROR - attribute name is not unique:"
58
+ pp rec
59
+ puts "duplicate:"
60
+ pp h[key]
61
+ exit 1
62
+ end
63
+ h[ key ] = rec
64
+ end
65
+ end
66
+ ## pp h
67
+ h
68
+ end
69
+
70
+
71
+ def build_recs( recs ) ## build and normalize (meta data) records
72
+ ## sort by id
73
+ recs = recs.sort do |l,r|
74
+ l['id'].to_i( 10 ) <=> r['id'].to_i( 10 ) # use base10 (decimal)
75
+ end
76
+
77
+ ## assert all recs are in order by id (0 to size)
78
+ recs.each_with_index do |rec, exp_id|
79
+ id = rec['id'].to_i(10)
80
+ if id != exp_id
81
+ puts "!! ERROR - meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
82
+ exit 1
83
+ end
84
+ end
85
+
86
+ ## convert to "wrapped / immutable" kind-of struct
87
+ recs = recs.map do |rec|
88
+ id = rec['id'].to_i( 10 )
89
+ name = normalize_name( rec['name'] )
90
+ type = rec['type']
91
+
92
+ more_names = (rec['more_names'] || '').split( '|' )
93
+ more_names = more_names.map {|str| normalize_name( str ) }
94
+
95
+ Metadata::Sprite.new(
96
+ id: id,
97
+ name: name,
98
+ type: type,
99
+ more_names: more_names )
100
+ end
101
+ recs
102
+ end # method build_recs
103
+
104
+
105
+
106
+ def initialize( image_path="./spritesheet.png",
107
+ meta_path="./spritesheet.csv",
108
+ width: 24,
109
+ height: 24 )
110
+ @width = width
111
+ @height = height
112
+
113
+ @sheet = ImageComposite.read( image_path, width: @width, height: @height )
114
+ recs = CsvHash.read( meta_path )
115
+
116
+ @recs = build_recs( recs )
117
+
118
+ ## lookup by "case/space-insensitive" name / key
119
+ @attributes_by_name = build_attributes_by_name( @recs )
120
+ end
121
+
122
+
123
+ def spritesheet() @sheet; end
124
+ alias_method :sheet, :spritesheet
125
+
126
+
127
+ def records() @recs; end
128
+ alias_method :meta, :records
129
+
130
+
131
+
132
+
133
+ def find_meta( q )
134
+ key = normalize_key( q ) ## normalize q(uery) string/symbol
135
+
136
+ rec = @attributes_by_name[ key ]
137
+ if rec
138
+ puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type}"
139
+ else
140
+ puts "!! WARN - no lookup found for key >#{key}<"
141
+ end
142
+
143
+ rec
144
+ end
145
+
146
+ def find( q )
147
+ rec = find_meta( q )
148
+
149
+ ## return image if record found
150
+ rec ? @sheet[ rec.id ] : nil
151
+ end
152
+
153
+
154
+ def to_recs( *values )
155
+ recs = []
156
+
157
+ attribute_names = values
158
+
159
+ attribute_names.each do |attribute_name|
160
+ attribute = find_meta( attribute_name )
161
+ if attribute.nil?
162
+ puts "!! ERROR - attribute >#{attribute_name}< not found; sorry"
163
+ exit 1
164
+ end
165
+ recs << attribute
166
+ end
167
+
168
+ recs
169
+ end
170
+
171
+
172
+
173
+ def generate_image( *values, background: nil, before: nil )
174
+ ## note: generate_image NO longer supports
175
+ ## - generate by integer number (indexes), sorry
176
+
177
+ recs = to_recs( *values )
178
+
179
+ ## note: first construct/generate image on transparent background
180
+ ## add background if present as LAST step
181
+ img = Image.new( @width, @height )
182
+
183
+ recs.each do |rec|
184
+ ## note: before call(back) MUST change image INPLACE!!!!
185
+ before.call( img, rec ) if before
186
+ img.compose!( @sheet[ rec.id ] )
187
+ end
188
+
189
+ if background ## for now assume background is (simply) color
190
+ img2 = Image.new( @width, @height )
191
+ img2.compose!( Image.new( @width, @height, background ) )
192
+ img2.compose!( img )
193
+ img = img2
194
+ end
195
+
196
+ img
197
+ end
198
+ alias_method :generate, :generate_image
199
+
200
+ end # class Generator
201
+
202
+ end # module Pixelart
@@ -0,0 +1,120 @@
1
+ module Pixelart
2
+
3
+ class Image
4
+
5
+ def self.calc_sample_steps( width, new_width,
6
+ center: true,
7
+ debug: false )
8
+ ## todo/fix: assert new_width is smaller than width
9
+ if debug
10
+ puts
11
+ puts "==> from: #{width}px to: #{new_width}px"
12
+ end
13
+
14
+ indexes = []
15
+
16
+ base_step = width / new_width ## pixels per pixel
17
+
18
+ err_step = (width % new_width) * 2 ## multiply by 2
19
+ denominator = new_width * 2 # denominator (in de - nenner e.g. 1/nenner 4/nenner)
20
+
21
+ overflow = err_step*new_width/denominator ## todo/check - assert that div is always WITHOUT remainder!!!!!
22
+
23
+ if debug
24
+ puts
25
+ puts "base_step (pixels per pixel):"
26
+ puts " #{base_step} - #{base_step} * #{new_width}px = #{base_step*new_width}px"
27
+ puts "err_step (in 1/#{width}*2):"
28
+ puts " #{err_step} / #{denominator} - #{err_step*new_width} / #{denominator} = +#{err_step*new_width/denominator}px overflow"
29
+ puts
30
+ end
31
+
32
+ # initial pixel offset
33
+ index = 0
34
+ err = err_step/2 ## note: start off with +err_step/2 to add overflow pixel in the "middle"
35
+
36
+
37
+ index += if center.is_a?( Integer )
38
+ center
39
+ elsif center
40
+ base_step/2
41
+ else
42
+ 0 # use 0px offset
43
+ end
44
+
45
+
46
+ new_width.times do |i|
47
+ if err >= denominator ## overflow
48
+ puts " -- overflow #{err}/#{denominator} - add +1 pixel offset to #{i}" if debug
49
+ index += 1
50
+ err -= denominator
51
+ end
52
+
53
+ puts " #{i} => #{index} -- #{err} / #{denominator}" if debug
54
+
55
+
56
+ indexes[i] = index
57
+
58
+ index += base_step
59
+ err += err_step
60
+ end
61
+
62
+ indexes
63
+ end
64
+
65
+
66
+
67
+ ## todo/check: rename to sample to resample or downsample - why? why not?
68
+ def sample( steps_x, steps_y=steps_x,
69
+ top_x: 0, top_y: 0 )
70
+ width = steps_x.size
71
+ height = steps_y.size
72
+ puts " downsampling from #{self.width}x#{self.height} to #{width}x#{height}..."
73
+
74
+ dest = Image.new( width, height )
75
+
76
+ steps_x.each_with_index do |step_x, x|
77
+ steps_y.each_with_index do |step_y, y|
78
+ pixel = self[top_x+step_x, top_y+step_y]
79
+
80
+ dest[x,y] = pixel
81
+ end
82
+ end
83
+
84
+ dest
85
+ end
86
+ alias_method :pixelate, :sample
87
+
88
+
89
+ def sample_debug( steps_x, steps_y=steps_x,
90
+ color: Color.parse( '#ffff00' ),
91
+ top_x: 0,
92
+ top_y: 0) ## add a yellow pixel
93
+
94
+ ## todo/fix: get a clone of the image (DO NOT modify in place)
95
+
96
+ img = self
97
+
98
+ steps_x.each_with_index do |step_x, x|
99
+ steps_y.each_with_index do |step_y, y|
100
+ base_x = top_x+step_x
101
+ base_y = top_y+step_y
102
+
103
+ img[base_x,base_y] = color
104
+
105
+ ## add more colors
106
+ img[base_x+1,base_y] = color
107
+ img[base_x+2,base_y] = color
108
+
109
+ img[base_x,base_y+1] = color
110
+ img[base_x,base_y+2] = color
111
+ end
112
+ end
113
+
114
+ self
115
+ end
116
+ alias_method :pixelate_debug, :sample_debug
117
+
118
+ end # class Image
119
+ end # module Pixelart
120
+
@@ -3,7 +3,7 @@ module Pixelart
3
3
 
4
4
  MAJOR = 1
5
5
  MINOR = 3
6
- PATCH = 0
6
+ PATCH = 3
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
data/lib/pixelart.rb CHANGED
@@ -9,4 +9,14 @@ require 'pixelart/base' # aka "strict(er)" version
9
9
  include Pixelart
10
10
 
11
11
 
12
+ ##########
13
+ # add some spelling convenience variants
14
+
15
+
16
+ module Pixelart
17
+ Palette256Image = Palette8BitImage = Palette8bitImage =
18
+ ImagePalette256 = ImagePalette8Bit = ImagePalette8bit
19
+
20
+ CompositeImage = ImageComposite
21
+ end
12
22
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pixelart
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-03 00:00:00.000000000 Z
11
+ date: 2022-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pixelart-colors
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: chunky_png
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -104,14 +118,13 @@ files:
104
118
  - lib/pixelart/base.rb
105
119
  - lib/pixelart/blur.rb
106
120
  - lib/pixelart/circle.rb
107
- - lib/pixelart/color.rb
108
121
  - lib/pixelart/composite.rb
109
- - lib/pixelart/gradient.rb
122
+ - lib/pixelart/generator.rb
110
123
  - lib/pixelart/image.rb
111
124
  - lib/pixelart/led.rb
112
125
  - lib/pixelart/misc.rb
113
- - lib/pixelart/palette.rb
114
126
  - lib/pixelart/pixelator.rb
127
+ - lib/pixelart/sample.rb
115
128
  - lib/pixelart/silhouette.rb
116
129
  - lib/pixelart/sketch.rb
117
130
  - lib/pixelart/spots.rb
@@ -1,131 +0,0 @@
1
- module Pixelart
2
-
3
-
4
- class Color
5
- TRANSPARENT = 0 # rgba( 0, 0, 0, 0)
6
- BLACK = 0xff # rgba( 0, 0, 0,255)
7
- WHITE = 0xffffffff # rgba(255,255,255,255)
8
-
9
-
10
- def self.parse( color )
11
- if color.is_a?( Integer ) ## e.g. assumes ChunkyPNG::Color.rgb() or such
12
- color ## pass through as is 1:1
13
- elsif color.is_a?( Array ) ## assume array of hsl(a) e. g. [180, 0.86, 0.88]
14
- from_hsl( *color )
15
- elsif color.is_a?( String )
16
- if color.downcase == 'transparent' ## special case for builtin colors
17
- TRANSPARENT
18
- else
19
- ## note: return an Integer !!! (not a Color class or such!!! )
20
- from_hex( color )
21
- end
22
- else
23
- raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
24
- end
25
- end
26
-
27
- def self.from_hex( hex )
28
- ## Creates a color by converting it from a string in hex notation.
29
- ##
30
- ## It supports colors with (#rrggbbaa) or without (#rrggbb)
31
- ## alpha channel as well as the 3-digit short format (#rgb)
32
- ## for those without. Color strings may include
33
- ## the prefix "0x" or "#"".
34
- ChunkyPNG::Color.from_hex( hex )
35
- end
36
-
37
- def self.from_hsl( hue, saturation, lightness, alpha=255)
38
- ChunkyPNG::Color.from_hsl( hue,
39
- saturation,
40
- lightness,
41
- alpha )
42
- end
43
-
44
-
45
- def self.to_hex( color, include_alpha: true )
46
- ChunkyPNG::Color.to_hex( color, include_alpha )
47
- end
48
-
49
- def self.to_hsl( color, include_alpha: true )
50
- # Returns an array with the separate HSL components of a color.
51
- ChunkyPNG::Color.to_hsl( color, include_alpha )
52
- end
53
-
54
- def self.r( color ) ChunkyPNG::Color.r( color ); end
55
- def self.g( color ) ChunkyPNG::Color.g( color ); end
56
- def self.b( color ) ChunkyPNG::Color.b( color ); end
57
-
58
- def self.rgb( r, g, b ) ChunkyPNG::Color.rgb( r, g, b); end
59
-
60
-
61
-
62
- ## known built-in color names
63
- def self.build_names
64
- names = {
65
- '#00000000' => 'TRANSPARENT',
66
- '#000000ff' => 'BLACK',
67
- '#ffffffff' => 'WHITE',
68
- }
69
-
70
- ## auto-add grayscale 1 to 254
71
- (1..254).each do |n|
72
- hex = "#" + ('%02x' % n)*3
73
- hex << "ff" ## add alpha channel (255)
74
- names[ hex ] = "8-BIT GRAYSCALE ##{n}"
75
- end
76
-
77
- names
78
- end
79
-
80
- NAMES = build_names
81
-
82
-
83
-
84
- def self.format( color )
85
- rgb = [r(color),
86
- g(color),
87
- b(color)]
88
-
89
- # rgb in hex (string format)
90
- # note: do NOT include alpha channel for now - why? why not?
91
- hex = "#" + rgb.map{|num| '%02x' % num }.join
92
-
93
- hsl = to_hsl( color )
94
- ## get alpha channel (transparency) for hsla
95
- alpha = hsl[3]
96
-
97
-
98
- buf = ''
99
- buf << hex
100
- buf << " / "
101
- buf << "rgb("
102
- buf << "%3d " % rgb[0]
103
- buf << "%3d " % rgb[1]
104
- buf << "%3d)" % rgb[2]
105
- buf << " - "
106
- buf << "hsl("
107
- buf << "%3d° " % (hsl[0] % 360)
108
- buf << "%3d%% " % (hsl[1]*100+0.5).to_i
109
- buf << "%3d%%)" % (hsl[2]*100+0.5).to_i
110
-
111
- if alpha != 255
112
- buf << " - α(%3d%%)" % (alpha*100/255+0.5).to_i
113
- else
114
- buf << " " ## add empty for 255 (full opacity)
115
- end
116
-
117
- ## note: add alpha channel to hex
118
- alpha_hex = '%02x' % alpha
119
- name = NAMES[ hex+alpha_hex ]
120
- buf << " - #{name}" if name
121
-
122
- buf
123
- end
124
- class << self
125
- alias_method :fmt, :format
126
- end
127
-
128
- end # class Color
129
- end # module Pixelart
130
-
131
-
@@ -1,106 +0,0 @@
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,72 +0,0 @@
1
- module Pixelart
2
-
3
-
4
- class Palette8bit # or use Palette256 alias?
5
-
6
-
7
- ## auto-add grayscale 0 to 255
8
- ## e.g. rgb(0,0,0)
9
- ## rgb(1,1,1)
10
- ## rgb(2,2,2)
11
- ## ...
12
- ## rgb(255,255,255)
13
- GRAYSCALE = (0..255).map { |n| Color.rgb( n, n, n ) }
14
-
15
-
16
- ## 8x32 gradient color stops
17
- ## see https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
18
-
19
- SEPIA_STOPS = [
20
- ['080400', '262117'],
21
- ['272218', '453E2F'],
22
- ['463F30', '645C48'],
23
- ['655D48', '837A60'],
24
-
25
- ['847A60', 'A29778'],
26
- ['A39878', 'C1B590'],
27
- ['C2B691', 'E0D2A8'],
28
- ['E1D3A9', 'FEEFBF'],
29
- ]
30
-
31
- BLUE_STOPS = [
32
- ['000000', '001F3E'],
33
- ['002040', '003F7E'],
34
- ['004080', '005FBD'],
35
- ['0060BF', '007FFD'],
36
-
37
- ['0080FF', '009FFF'],
38
- ['00A0FF', '00BFFF'],
39
- ['00C0FF', '00DFFF'],
40
- ['00E0FF', '00FEFF'],
41
- ]
42
-
43
- FALSE_STOPS = [
44
- ['FF00FF', '6400FF'],
45
- ['5F00FF', '003CFF'],
46
- ['0041FF', '00DCFF'],
47
- ['00E1FF', '00FF82'],
48
-
49
- ['00FF7D', '1EFF00'],
50
- ['23FF00', 'BEFF00'],
51
- ['C3FF00', 'FFA000'],
52
- ['FF9B00', 'FF0000'],
53
- ]
54
-
55
-
56
- def self.build_palette( gradients )
57
- colors_per_gradient, mod = 256.divmod( gradients.size )
58
- raise ArgumentError, "8bit palette - 256 must be divisible by # of gradients (#{gradients.size}; expected mod of 0 but got #{mod}" if mod != 0
59
-
60
- colors = []
61
- gradients.each do |stops|
62
- colors += Gradient.new( *stops ).colors( colors_per_gradient )
63
- end
64
- colors
65
- end
66
-
67
- SEPIA = build_palette( SEPIA_STOPS )
68
- BLUE = build_palette( BLUE_STOPS )
69
- FALSE = build_palette( FALSE_STOPS )
70
- end # class Palette8bit
71
- end # module Pixelart
72
-