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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -3
- data/Manifest.txt +5 -0
- data/README.md +290 -290
- data/Rakefile +32 -32
- data/lib/pixelart/base.rb +93 -84
- data/lib/pixelart/blur.rb +19 -19
- data/lib/pixelart/circle.rb +46 -0
- data/lib/pixelart/color.rb +131 -131
- data/lib/pixelart/composite.rb +154 -154
- data/lib/pixelart/generator.rb +199 -0
- data/lib/pixelart/gradient.rb +106 -106
- data/lib/pixelart/image.rb +283 -272
- data/lib/pixelart/led.rb +37 -37
- data/lib/pixelart/misc.rb +66 -66
- data/lib/pixelart/palette.rb +72 -72
- data/lib/pixelart/pixelator.rb +165 -165
- data/lib/pixelart/sample.rb +120 -0
- data/lib/pixelart/silhouette.rb +35 -35
- data/lib/pixelart/sketch.rb +69 -69
- data/lib/pixelart/spots.rb +146 -146
- data/lib/pixelart/stripes.rb +116 -0
- data/lib/pixelart/transparent.rb +60 -60
- data/lib/pixelart/ukraine.rb +20 -0
- data/lib/pixelart/vector.rb +163 -163
- data/lib/pixelart/version.rb +22 -22
- data/lib/pixelart.rb +12 -12
- metadata +11 -6
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
|
+
|
data/lib/pixelart/palette.rb
CHANGED
@@ -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
|
+
|
data/lib/pixelart/pixelator.rb
CHANGED
@@ -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
|
+
|