pixelart 1.2.3 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -3
- data/Manifest.txt +1 -0
- data/README.md +290 -290
- data/Rakefile +32 -32
- data/lib/pixelart/base.rb +87 -86
- data/lib/pixelart/blur.rb +19 -19
- data/lib/pixelart/circle.rb +46 -46
- data/lib/pixelart/color.rb +131 -131
- data/lib/pixelart/composite.rb +154 -154
- data/lib/pixelart/gradient.rb +106 -106
- data/lib/pixelart/image.rb +283 -283
- 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/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 -33
- data/lib/pixelart/vector.rb +163 -163
- data/lib/pixelart/version.rb +22 -22
- data/lib/pixelart.rb +12 -12
- metadata +7 -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
|
+
|
data/lib/pixelart/silhouette.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
module Pixelart
|
2
|
-
|
3
|
-
|
4
|
-
## todo/check:
|
5
|
-
## use a different name for silhouette
|
6
|
-
## - why not - outline ???
|
7
|
-
## or - shadow ???
|
8
|
-
## or - profile ???
|
9
|
-
## or - figure ???
|
10
|
-
## or - shape ???
|
11
|
-
## or - form ???
|
12
|
-
|
13
|
-
class Image
|
14
|
-
def silhouette( color='#000000' )
|
15
|
-
color = Color.parse( color )
|
16
|
-
|
17
|
-
img = Image.new( @img.width, @img.height )
|
18
|
-
|
19
|
-
@img.width.times do |x|
|
20
|
-
@img.height.times do |y|
|
21
|
-
pixel = @img[x,y]
|
22
|
-
|
23
|
-
img[x,y] = if pixel == Color::TRANSPARENT # transparent (0)
|
24
|
-
Color::TRANSPARENT
|
25
|
-
else
|
26
|
-
color
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
img
|
31
|
-
end
|
32
|
-
|
33
|
-
end # class Image
|
34
|
-
|
35
|
-
end # module Pixelart
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
|
4
|
+
## todo/check:
|
5
|
+
## use a different name for silhouette
|
6
|
+
## - why not - outline ???
|
7
|
+
## or - shadow ???
|
8
|
+
## or - profile ???
|
9
|
+
## or - figure ???
|
10
|
+
## or - shape ???
|
11
|
+
## or - form ???
|
12
|
+
|
13
|
+
class Image
|
14
|
+
def silhouette( color='#000000' )
|
15
|
+
color = Color.parse( color )
|
16
|
+
|
17
|
+
img = Image.new( @img.width, @img.height )
|
18
|
+
|
19
|
+
@img.width.times do |x|
|
20
|
+
@img.height.times do |y|
|
21
|
+
pixel = @img[x,y]
|
22
|
+
|
23
|
+
img[x,y] = if pixel == Color::TRANSPARENT # transparent (0)
|
24
|
+
Color::TRANSPARENT
|
25
|
+
else
|
26
|
+
color
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
img
|
31
|
+
end
|
32
|
+
|
33
|
+
end # class Image
|
34
|
+
|
35
|
+
end # module Pixelart
|