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.
data/lib/pixelart/misc.rb CHANGED
@@ -1,66 +1,66 @@
1
- module Pixelart
2
-
3
-
4
- class ImagePalette8bit < Image # or use Palette256 alias?
5
- def initialize( colors, size: 1, spacing: nil )
6
- ## todo/check: change size arg to pixel or such? better name/less confusing - why? why not?
7
-
8
- ## todo/check: assert colors MUST have 256 colors!!!!
9
-
10
- ## use a "smart" default if no spacing set
11
- ## 0 for for (pixel) size == 1
12
- ## 1 for the rest
13
- spacing = size == 1 ? 0 : 1 if spacing.nil?
14
-
15
- img = ChunkyPNG::Image.new( 32*size+(32-1)*spacing,
16
- 8*size+(8-1)*spacing )
17
-
18
-
19
- colors =colors.map {|color| Color.parse( color ) }
20
-
21
- colors.each_with_index do |color,i|
22
- y,x = i.divmod( 32 )
23
- if size > 1
24
- size.times do |n|
25
- size.times do |m|
26
- img[ x*size+n+spacing*x,
27
- y*size+m+spacing*y] = color
28
- end
29
- end
30
- else
31
- img[x,y] = color
32
- end
33
- end
34
-
35
- super( img.width, img.height, img )
36
- end
37
- end # class ImagePalette8bit
38
-
39
-
40
-
41
- class ImageColorBar < Image
42
- ## make a color bar
43
- ## keep auto-zoom 24x or such - why? why not?
44
- def initialize( colors, size: 24 )
45
- img = ChunkyPNG::Image.new( colors.size*size,
46
- size,
47
- ChunkyPNG::Color::WHITE ) # why? why not?
48
-
49
- colors = colors.map {|color| Color.parse( color ) }
50
-
51
- colors.each_with_index do |color,i|
52
- size.times do |x|
53
- size.times do |y|
54
- img[x+size*i,y] = color
55
- end
56
- end
57
- end
58
-
59
- super( img.width, img.height, img )
60
- end
61
- end # class ImageColorBar
62
-
63
-
64
- end # module Pixelart
65
-
66
-
1
+ module Pixelart
2
+
3
+
4
+ class ImagePalette8bit < Image # or use Palette256 alias?
5
+ def initialize( colors, size: 1, spacing: nil )
6
+ ## todo/check: change size arg to pixel or such? better name/less confusing - why? why not?
7
+
8
+ ## todo/check: assert colors MUST have 256 colors!!!!
9
+
10
+ ## use a "smart" default if no spacing set
11
+ ## 0 for for (pixel) size == 1
12
+ ## 1 for the rest
13
+ spacing = size == 1 ? 0 : 1 if spacing.nil?
14
+
15
+ img = ChunkyPNG::Image.new( 32*size+(32-1)*spacing,
16
+ 8*size+(8-1)*spacing )
17
+
18
+
19
+ colors =colors.map {|color| Color.parse( color ) }
20
+
21
+ colors.each_with_index do |color,i|
22
+ y,x = i.divmod( 32 )
23
+ if size > 1
24
+ size.times do |n|
25
+ size.times do |m|
26
+ img[ x*size+n+spacing*x,
27
+ y*size+m+spacing*y] = color
28
+ end
29
+ end
30
+ else
31
+ img[x,y] = color
32
+ end
33
+ end
34
+
35
+ super( img.width, img.height, img )
36
+ end
37
+ end # class ImagePalette8bit
38
+
39
+
40
+
41
+ class ImageColorBar < Image
42
+ ## make a color bar
43
+ ## keep auto-zoom 24x or such - why? why not?
44
+ def initialize( colors, size: 24 )
45
+ img = ChunkyPNG::Image.new( colors.size*size,
46
+ size,
47
+ ChunkyPNG::Color::WHITE ) # why? why not?
48
+
49
+ colors = colors.map {|color| Color.parse( color ) }
50
+
51
+ colors.each_with_index do |color,i|
52
+ size.times do |x|
53
+ size.times do |y|
54
+ img[x+size*i,y] = color
55
+ end
56
+ end
57
+ end
58
+
59
+ super( img.width, img.height, img )
60
+ end
61
+ end # class ImageColorBar
62
+
63
+
64
+ end # module Pixelart
65
+
66
+
@@ -1,72 +1,72 @@
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
-
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
+
@@ -1,165 +1,165 @@
1
- module Pixelart
2
-
3
-
4
- class Pixelator # or use Minifier or such - rename - why? why not?
5
-
6
- def initialize( img, width=24, height=24 )
7
- @img = img.is_a?( Image ) ? img.image : img ## "unwrap" if Pixelart::Image
8
- @width = width
9
- @height = height
10
-
11
- ## calculate pixel size / density / resolution
12
- ## how many pixels per pixel?
13
- @xsize, @xoverflow = img.width.divmod( width )
14
- @ysize, @yoverflow = img.height.divmod( height )
15
-
16
- puts "minify image size from (#{@img.width}x#{@img.height}) to (#{width}x#{height})"
17
- puts " pixel size (#{@xsize}x#{@ysize}) - #{@xsize*@ysize} pixel(s) per pixel"
18
- puts " overflow x: #{@xoverflow}, y: #{@yoverflow} pixel(s)" if @xoverflow > 0 || @yoverflow > 0
19
- end
20
-
21
-
22
- def grid( spacing: 10 )
23
- width = @img.width + (@width-1)*spacing
24
- height = @img.height + (@height-1)*spacing
25
-
26
- img = ChunkyPNG::Image.new( width, height, ChunkyPNG::Color::WHITE )
27
-
28
- @img.width.times do |x|
29
- xpixel = x/@xsize
30
- @img.height.times do |y|
31
- ypixel = y/@ysize
32
-
33
- ## clip overflow pixels
34
- xpixel = @width-1 if xpixel >= @width
35
- ypixel = @height-1 if ypixel >= @height
36
-
37
- color = @img[x,y]
38
- img[x + spacing*xpixel,
39
- y + spacing*ypixel] = color
40
- end
41
- end
42
-
43
- Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
44
- end
45
-
46
-
47
- # pixels by coordinates (x/y) with color statistics / usage
48
- def pixels
49
- @pixels ||= begin
50
- pixels = []
51
- @img.width.times do |x|
52
- xpixel = x/@xsize
53
- @img.height.times do |y|
54
- ypixel = y/@ysize
55
-
56
- ## skip/cut off overflow pixels
57
- next if xpixel >= @width || ypixel >= @height
58
-
59
- color = @img[x,y]
60
- colors = pixels[xpixel+ypixel*@width] ||= Hash.new(0)
61
- colors[ color ] += 1
62
- end
63
- end
64
-
65
- ## sort pixel colors by usage / count (highest first)
66
- pixels = pixels.map do |pixel|
67
- pixel.sort do |l,r|
68
- r[1] <=> l[1]
69
- end.to_h
70
- end
71
- pixels
72
- end
73
- end
74
-
75
- def pixel(x,y) pixels[x+y*@width]; end
76
- alias_method :[], :pixel
77
-
78
-
79
- def can_pixelate?( threshold: 50 )
80
- # check if any pixel has NOT a color with a 50% majority?
81
- count = 0
82
- @width.times do |x|
83
- @height.times do |y|
84
- pixel = pixel( x, y )
85
- sum = pixel.values.sum
86
- color_count = pixel.values[0]
87
-
88
- threshold_count = sum / (100/threshold)
89
- if color_count < threshold_count
90
- count += 1
91
- puts "!! #{color_count} < #{threshold_count} (#{threshold}%)"
92
- ## todo/check: stor warn in a public errors or warns array - why? why not?
93
- puts "!! WARN #{count} - pixel (#{x}/#{y}) - no majority (#{threshold}%) color:"
94
- pp pixel
95
- end
96
- end
97
- end
98
-
99
- count == 0 ## return true if not warnings found
100
- end
101
- alias_method :pixelate?, :can_pixelate?
102
-
103
-
104
- def pixelate
105
- img = ChunkyPNG::Image.new( @width, @height )
106
-
107
- @width.times do |x|
108
- @height.times do |y|
109
- pixel = pixel( x, y )
110
- color = pixel.keys[0]
111
- img[x,y] = color
112
- end
113
- end
114
-
115
- Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
116
- end
117
-
118
- def outline
119
- ## create a two color outline (transparent and non-transparent color)
120
- img = ChunkyPNG::Image.new( @width, @height )
121
-
122
- @width.times do |x|
123
- @height.times do |y|
124
- pixel = pixel( x, y )
125
- ## calculate pixel count for transparent and non-transparent parts
126
- ## note:
127
- ## also count all colors with alpha channel < 200 to transparent!!
128
- transparent_count, color_count = pixel.reduce([0,0]) do |mem, (color,count)|
129
- hsl = Color.to_hsl( color )
130
- ## get alpha channel (transparency) for hsla
131
- ## 0-255 max.
132
- alpha = hsl[3]
133
- if color == 0x00 || alpha < 200
134
- mem[0] += count
135
- else
136
- mem[1] += count
137
- end
138
- mem
139
- end
140
-
141
- print "."
142
- if transparent_count > 0 && color_count > 0
143
- print "(#{x}/#{y}=>#{transparent_count}/#{color_count})"
144
- end
145
-
146
- ## todo/check:
147
- ## warn if sum_transparent == sum_color
148
- ## or within "threshold" e.g. below 55% or 58% or such - why? why not?
149
- ## or add treshold as param to outline?
150
- color = if transparent_count > color_count
151
- 0x0
152
- else
153
- 0x0000ffff ## use blue for now
154
- end
155
-
156
- img[x,y] = color
157
- end
158
- end
159
- print "\n"
160
-
161
- Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
162
- end
163
- end # class Pixelator
164
- end # module Pixelart
165
-
1
+ module Pixelart
2
+
3
+
4
+ class Pixelator # or use Minifier or such - rename - why? why not?
5
+
6
+ def initialize( img, width=24, height=24 )
7
+ @img = img.is_a?( Image ) ? img.image : img ## "unwrap" if Pixelart::Image
8
+ @width = width
9
+ @height = height
10
+
11
+ ## calculate pixel size / density / resolution
12
+ ## how many pixels per pixel?
13
+ @xsize, @xoverflow = img.width.divmod( width )
14
+ @ysize, @yoverflow = img.height.divmod( height )
15
+
16
+ puts "minify image size from (#{@img.width}x#{@img.height}) to (#{width}x#{height})"
17
+ puts " pixel size (#{@xsize}x#{@ysize}) - #{@xsize*@ysize} pixel(s) per pixel"
18
+ puts " overflow x: #{@xoverflow}, y: #{@yoverflow} pixel(s)" if @xoverflow > 0 || @yoverflow > 0
19
+ end
20
+
21
+
22
+ def grid( spacing: 10 )
23
+ width = @img.width + (@width-1)*spacing
24
+ height = @img.height + (@height-1)*spacing
25
+
26
+ img = ChunkyPNG::Image.new( width, height, ChunkyPNG::Color::WHITE )
27
+
28
+ @img.width.times do |x|
29
+ xpixel = x/@xsize
30
+ @img.height.times do |y|
31
+ ypixel = y/@ysize
32
+
33
+ ## clip overflow pixels
34
+ xpixel = @width-1 if xpixel >= @width
35
+ ypixel = @height-1 if ypixel >= @height
36
+
37
+ color = @img[x,y]
38
+ img[x + spacing*xpixel,
39
+ y + spacing*ypixel] = color
40
+ end
41
+ end
42
+
43
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
44
+ end
45
+
46
+
47
+ # pixels by coordinates (x/y) with color statistics / usage
48
+ def pixels
49
+ @pixels ||= begin
50
+ pixels = []
51
+ @img.width.times do |x|
52
+ xpixel = x/@xsize
53
+ @img.height.times do |y|
54
+ ypixel = y/@ysize
55
+
56
+ ## skip/cut off overflow pixels
57
+ next if xpixel >= @width || ypixel >= @height
58
+
59
+ color = @img[x,y]
60
+ colors = pixels[xpixel+ypixel*@width] ||= Hash.new(0)
61
+ colors[ color ] += 1
62
+ end
63
+ end
64
+
65
+ ## sort pixel colors by usage / count (highest first)
66
+ pixels = pixels.map do |pixel|
67
+ pixel.sort do |l,r|
68
+ r[1] <=> l[1]
69
+ end.to_h
70
+ end
71
+ pixels
72
+ end
73
+ end
74
+
75
+ def pixel(x,y) pixels[x+y*@width]; end
76
+ alias_method :[], :pixel
77
+
78
+
79
+ def can_pixelate?( threshold: 50 )
80
+ # check if any pixel has NOT a color with a 50% majority?
81
+ count = 0
82
+ @width.times do |x|
83
+ @height.times do |y|
84
+ pixel = pixel( x, y )
85
+ sum = pixel.values.sum
86
+ color_count = pixel.values[0]
87
+
88
+ threshold_count = sum / (100/threshold)
89
+ if color_count < threshold_count
90
+ count += 1
91
+ puts "!! #{color_count} < #{threshold_count} (#{threshold}%)"
92
+ ## todo/check: stor warn in a public errors or warns array - why? why not?
93
+ puts "!! WARN #{count} - pixel (#{x}/#{y}) - no majority (#{threshold}%) color:"
94
+ pp pixel
95
+ end
96
+ end
97
+ end
98
+
99
+ count == 0 ## return true if not warnings found
100
+ end
101
+ alias_method :pixelate?, :can_pixelate?
102
+
103
+
104
+ def pixelate
105
+ img = ChunkyPNG::Image.new( @width, @height )
106
+
107
+ @width.times do |x|
108
+ @height.times do |y|
109
+ pixel = pixel( x, y )
110
+ color = pixel.keys[0]
111
+ img[x,y] = color
112
+ end
113
+ end
114
+
115
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
116
+ end
117
+
118
+ def outline
119
+ ## create a two color outline (transparent and non-transparent color)
120
+ img = ChunkyPNG::Image.new( @width, @height )
121
+
122
+ @width.times do |x|
123
+ @height.times do |y|
124
+ pixel = pixel( x, y )
125
+ ## calculate pixel count for transparent and non-transparent parts
126
+ ## note:
127
+ ## also count all colors with alpha channel < 200 to transparent!!
128
+ transparent_count, color_count = pixel.reduce([0,0]) do |mem, (color,count)|
129
+ hsl = Color.to_hsl( color )
130
+ ## get alpha channel (transparency) for hsla
131
+ ## 0-255 max.
132
+ alpha = hsl[3]
133
+ if color == 0x00 || alpha < 200
134
+ mem[0] += count
135
+ else
136
+ mem[1] += count
137
+ end
138
+ mem
139
+ end
140
+
141
+ print "."
142
+ if transparent_count > 0 && color_count > 0
143
+ print "(#{x}/#{y}=>#{transparent_count}/#{color_count})"
144
+ end
145
+
146
+ ## todo/check:
147
+ ## warn if sum_transparent == sum_color
148
+ ## or within "threshold" e.g. below 55% or 58% or such - why? why not?
149
+ ## or add treshold as param to outline?
150
+ color = if transparent_count > color_count
151
+ 0x0
152
+ else
153
+ 0x0000ffff ## use blue for now
154
+ end
155
+
156
+ img[x,y] = color
157
+ end
158
+ end
159
+ print "\n"
160
+
161
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
162
+ end
163
+ end # class Pixelator
164
+ end # module Pixelart
165
+