pxlsrt 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/pxlsrt +40 -0
- data/lib/pxlsrt/brute.rb +137 -0
- data/lib/pxlsrt/colors.rb +183 -0
- data/lib/pxlsrt/helpers.rb +49 -0
- data/lib/pxlsrt/smart.rb +219 -0
- data/lib/pxlsrt/version.rb +3 -0
- data/lib/pxlsrt.rb +5 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c2b3280ae7ad02daa4da266d7da0754a3d7723b
|
4
|
+
data.tar.gz: 626ec7cf24c9c746e2cdc39f65acd300e2485330
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0e813eab6fb0931f8448cdc4c389c44fdc0cf958b32304ac8da870be94b5dd5422a925f1d72327b9c629b825eb41d31b6b88aa8d4f09e55c4d45d1a545880127
|
7
|
+
data.tar.gz: 77cb11792128cd2dfd458f76fa8f7adc0dbf1f0fed6bddd679a56bdfa5d8a81e93bb207e3f74a316c8b838052ee07c83d530d04a0bd89270dbc282a6de337cb7
|
data/bin/pxlsrt
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'pxlsrt'
|
5
|
+
require 'thor'
|
6
|
+
|
7
|
+
class PXLSRT < Thor
|
8
|
+
class_option :reverse, :type => :string, :default => "no", :banner => "[no | reverse | either]", :aliases => "-r", :enum => ["no", "reverse", "either"]
|
9
|
+
class_option :vertical, :type => :boolean, :default => false, :aliases => "-v"
|
10
|
+
class_option :diagonal, :type => :boolean, :default => false, :aliases => "-d"
|
11
|
+
class_option :smooth, :type => :boolean, :default => false, :aliases => "-s"
|
12
|
+
class_option :method, :type => :string, :default => "sum-rgb", :banner => "[sum-rgb | red | green | blue | sum-hsb | hue | saturation | brightness | uniqueness | luma | random]", :aliases => "-m", :enum => ["sum-rgb", "red", "green", "blue", "sum-hsb", "hue", "saturation", "brightness", "uniqueness", "luma", "random"]
|
13
|
+
class_option :diagonal, :type => :boolean, :default => false, :aliases => "-d"
|
14
|
+
class_option :verbose, :type => :boolean, :default => false, :aliases => "-V"
|
15
|
+
|
16
|
+
option :min, :type => :numeric, :default => Float::INFINITY, :banner => "MINIMUM BANDWIDTH"
|
17
|
+
option :max, :type => :numeric, :default => Float::INFINITY, :banner => "MAXIMUM BANDWIDTH"
|
18
|
+
desc "brute INPUT OUTPUT [options]", "Brute pixel sorting"
|
19
|
+
def brute(input, output)
|
20
|
+
k={}
|
21
|
+
for o in options.keys
|
22
|
+
k[o.to_sym]=options[o]
|
23
|
+
end
|
24
|
+
Pxlsrt::Brute.suite(input, output, true, k)
|
25
|
+
end
|
26
|
+
|
27
|
+
option :absolute, :type => :boolean, :default => false, :aliases => "-a", :banner => "ABSOLUTE EDGE FINDING"
|
28
|
+
option :threshold, :type => :numeric, :default => 20, :aliases => "-t"
|
29
|
+
option :edge, :type => :numeric, :default => 2, :aliases => "-e", :banner => "EDGE BUFFERING"
|
30
|
+
desc "smart INPUT OUTPUT [options]", "Smart pixel sorting"
|
31
|
+
def smart(input, output)
|
32
|
+
k={}
|
33
|
+
for o in options.keys
|
34
|
+
k[o.to_sym]=options[o]
|
35
|
+
end
|
36
|
+
Pxlsrt::Smart.suite(input, output, true, k)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
PXLSRT.start(ARGV)
|
data/lib/pxlsrt/brute.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'oily_png'
|
3
|
+
|
4
|
+
module Pxlsrt
|
5
|
+
class Brute
|
6
|
+
def self.suite(inputFileName, outputFileName, trusted, o={})
|
7
|
+
kml=Pxlsrt::Brute.brute(inputFileName, trusted, o)
|
8
|
+
if Pxlsrt::Helpers.contented(kml)
|
9
|
+
kml.save(outputFileName)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
def self.brute(input, trusted, o={})
|
13
|
+
startTime=Time.now
|
14
|
+
defOptions={
|
15
|
+
:reverse => "no",
|
16
|
+
:vertical => false,
|
17
|
+
:diagonal => false,
|
18
|
+
:smooth => false,
|
19
|
+
:method => "sum-rgb",
|
20
|
+
:verbose => false,
|
21
|
+
:min => Float::INFINITY,
|
22
|
+
:max => Float::INFINITY
|
23
|
+
}
|
24
|
+
defRules={
|
25
|
+
:reverse => ["no", "reverse", "either"],
|
26
|
+
:vertical => [false, true],
|
27
|
+
:diagonal => [false, true],
|
28
|
+
:smooth => [false, true],
|
29
|
+
:method => ["sum-rgb", "red", "green", "blue", "sum-hsb", "hue", "saturation", "brightness", "uniqueness", "luma", "random"],
|
30
|
+
:verbose => [false, true],
|
31
|
+
:min => [Float::INFINITY, {:class => [Fixnum]}],
|
32
|
+
:max => [Float::INFINITY, {:class => [Fixnum]}]
|
33
|
+
}
|
34
|
+
options=defOptions.merge(o)
|
35
|
+
if o.length==0 or trusted==true or (trusted==false and o.length!=0 and Pxlsrt::Helpers.checkOptions(options, defRules)!=false)
|
36
|
+
Pxlsrt::Helpers.verbose("Options are all good.") if options[:verbose]
|
37
|
+
if input.class==String
|
38
|
+
Pxlsrt::Helpers.verbose("Getting image from file...") if options[:verbose]
|
39
|
+
input=ChunkyPNG::Image.from_file(input)
|
40
|
+
elsif input.class!=String and input.class!=ChunkyPNG::Image
|
41
|
+
Pxlsrt::Helpers.error("Input is not a filename or ChunkyPNG::Image") if options[:verbose]
|
42
|
+
return
|
43
|
+
end
|
44
|
+
Pxlsrt::Helpers.verbose("Brute mode.") if options[:verbose]
|
45
|
+
case options[:reverse].downcase
|
46
|
+
when "reverse"
|
47
|
+
nre=1
|
48
|
+
when "either"
|
49
|
+
nre=-1
|
50
|
+
else
|
51
|
+
nre=0
|
52
|
+
end
|
53
|
+
png=input
|
54
|
+
w=png.dimension.width
|
55
|
+
h=png.dimension.height
|
56
|
+
sorted=ChunkyPNG::Image.new(w, h, ChunkyPNG::Color::TRANSPARENT)
|
57
|
+
Pxlsrt::Helpers.verbose("Retrieving RGB values of pixels...") if options[:verbose]
|
58
|
+
kml=[]
|
59
|
+
for xy in 0..(w*h-1)
|
60
|
+
kml.push(Pxlsrt::Colors.getRGB(png[xy % w,(xy/w).floor]))
|
61
|
+
end
|
62
|
+
if options[:vertical]==true
|
63
|
+
Pxlsrt::Helpers.verbose("Rotating image for vertical mode...") if options[:verbose]
|
64
|
+
kml=Pxlsrt::Colors.rotateImage(kml, w, h, 3)
|
65
|
+
w,h=h,w
|
66
|
+
end
|
67
|
+
toImage=[]
|
68
|
+
if !options[:diagonal]
|
69
|
+
Pxlsrt::Helpers.verbose("Pixel sorting using method '#{options[:method]}'...") if options[:verbose]
|
70
|
+
for m in Pxlsrt::Colors.imageRGBLines(kml, w)
|
71
|
+
sliceRanges=Pxlsrt::Colors.randomSlices(m, options[:min], options[:max])
|
72
|
+
newInTown=[]
|
73
|
+
if options[:smooth]!=true
|
74
|
+
for ranger in sliceRanges
|
75
|
+
newInTown.concat(Pxlsrt::Colors.pixelSort(m[ranger[0]..ranger[1]], options[:method].downcase, nre))
|
76
|
+
end
|
77
|
+
else
|
78
|
+
for ranger in sliceRanges
|
79
|
+
k=(m[ranger[0]..ranger[1]]).group_by { |x| x }
|
80
|
+
g=Pxlsrt::Colors.pixelSort(k.keys, options[:method].downcase, nre)
|
81
|
+
j=g.map { |x| k[x] }.flatten(1)
|
82
|
+
newInTown.concat(j)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
toImage.concat(newInTown)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
Pxlsrt::Helpers.verbose("Determining diagonals...") if options[:verbose]
|
89
|
+
dia=Pxlsrt::Colors.getDiagonals(kml,w,h)
|
90
|
+
Pxlsrt::Helpers.verbose("Pixel sorting using method '#{options[:method]}'...") if options[:verbose]
|
91
|
+
for m in dia.keys
|
92
|
+
sliceRanges=Pxlsrt::Colors.randomSlices(dia[m], options[:min], options[:max])
|
93
|
+
newInTown=[]
|
94
|
+
if options[:smooth]!=true
|
95
|
+
for ranger in sliceRanges
|
96
|
+
newInTown.concat(Pxlsrt::Colors.pixelSort(dia[m][ranger[0]..ranger[1]], options[:method].downcase, nre))
|
97
|
+
end
|
98
|
+
else
|
99
|
+
for ranger in sliceRanges
|
100
|
+
k=(dia[m][ranger[0]..ranger[1]]).group_by { |x| x }
|
101
|
+
g=Pxlsrt::Colors.pixelSort(k.keys, options[:method].downcase, nre)
|
102
|
+
j=g.map { |x| k[x] }.flatten(1)
|
103
|
+
newInTown.concat(j)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
dia[m]=newInTown
|
107
|
+
end
|
108
|
+
Pxlsrt::Helpers.verbose("Setting diagonals back to standard lines...") if options[:verbose]
|
109
|
+
toImage=Pxlsrt::Colors.fromDiagonals(dia,w)
|
110
|
+
end
|
111
|
+
if options[:vertical]==true
|
112
|
+
Pxlsrt::Helpers.verbose("Rotating back (because of vertical mode).") if options[:verbose]
|
113
|
+
toImage=Pxlsrt::Colors.rotateImage(toImage, w,h,1)
|
114
|
+
w,h=h,w
|
115
|
+
end
|
116
|
+
Pxlsrt::Helpers.verbose("Giving pixels new RGB values...") if options[:verbose]
|
117
|
+
for xy in 0..(w*h-1)
|
118
|
+
sorted[xy % w, (xy/w).floor]=Pxlsrt::Colors.arrayToRGB(toImage[xy])
|
119
|
+
end
|
120
|
+
endTime=Time.now
|
121
|
+
timeElapsed=endTime-startTime
|
122
|
+
if timeElapsed < 60
|
123
|
+
Pxlsrt::Helpers.verbose("Took #{timeElapsed.round(4)} second#{ timeElapsed!=1.0 ? "s" : "" }.") if options[:verbose]
|
124
|
+
else
|
125
|
+
minutes=(timeElapsed/60).floor
|
126
|
+
seconds=(timeElapsed % 60).round(4)
|
127
|
+
Pxlsrt::Helpers.verbose("Took #{minutes} minute#{ minutes!=1 ? "s" : "" } and #{seconds} second#{ seconds!=1.0 ? "s" : "" }.") if options[:verbose]
|
128
|
+
end
|
129
|
+
Pxlsrt::Helpers.verbose("Returning ChunkyPNG::Image...") if options[:verbose]
|
130
|
+
return sorted
|
131
|
+
else
|
132
|
+
Pxlsrt::Helpers.error("Options specified do not follow the correct format.") if options[:verbose]
|
133
|
+
return
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "oily_png"
|
2
|
+
|
3
|
+
module Pxlsrt
|
4
|
+
class Colors
|
5
|
+
def self.getRGB(pxl)
|
6
|
+
return [ChunkyPNG::Color.r(pxl), ChunkyPNG::Color.g(pxl), ChunkyPNG::Color.b(pxl)]
|
7
|
+
end
|
8
|
+
def self.rotateImage(what, width, height, a)
|
9
|
+
nu=[]
|
10
|
+
case a
|
11
|
+
when 0, 360, 4
|
12
|
+
nu=what
|
13
|
+
when 1, 90
|
14
|
+
for xy in 0..(what.length-1)
|
15
|
+
nu[((height-1)-(xy/width).floor)+(xy % width)*height]=what[xy]
|
16
|
+
end
|
17
|
+
when 2, 180
|
18
|
+
nu=what.reverse
|
19
|
+
when 3, 270
|
20
|
+
for xy in 0..(what.length-1)
|
21
|
+
nu[(xy/width).floor+((width-1)-(xy % width))*height]=what[xy]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
return nu
|
25
|
+
end
|
26
|
+
def self.imageRGBLines(image, width)
|
27
|
+
return image.each_slice(width).to_a
|
28
|
+
end
|
29
|
+
def self.randomSlices(arr, minLength, maxLength)
|
30
|
+
len=arr.length-1
|
31
|
+
if len!=0
|
32
|
+
min=[minLength, maxLength].min
|
33
|
+
max=[maxLength, minLength].max
|
34
|
+
if min > len
|
35
|
+
min=len
|
36
|
+
end
|
37
|
+
if max > len
|
38
|
+
max=len
|
39
|
+
end
|
40
|
+
nu=[[0, rand(min..max)]]
|
41
|
+
last=nu.first[1]
|
42
|
+
sorting=true
|
43
|
+
while sorting do
|
44
|
+
if (len-last) <= max
|
45
|
+
nu.push([last+1, len])
|
46
|
+
sorting=false
|
47
|
+
else
|
48
|
+
nu.push([last+1, last+1+rand(min..max)])
|
49
|
+
last=nu.last[1]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
nu=[[0,0]]
|
54
|
+
end
|
55
|
+
return nu
|
56
|
+
end
|
57
|
+
def self.pxldex(pxl)
|
58
|
+
return pxl[0]+pxl[1]+pxl[2]
|
59
|
+
end
|
60
|
+
def self.rgb2hsb(rgb)
|
61
|
+
r = rgb[0] / 255.0
|
62
|
+
g = rgb[1] / 255.0
|
63
|
+
b = rgb[2] / 255.0
|
64
|
+
max = [r, g, b].max
|
65
|
+
min = [r, g, b].min
|
66
|
+
delta = max - min
|
67
|
+
v = max * 100
|
68
|
+
if (max != 0.0)
|
69
|
+
s = delta / max *100
|
70
|
+
else
|
71
|
+
s = 0.0
|
72
|
+
end
|
73
|
+
if (s == 0.0)
|
74
|
+
h = 0.0
|
75
|
+
else
|
76
|
+
if (r == max)
|
77
|
+
h = (g - b) / delta
|
78
|
+
elsif (g == max)
|
79
|
+
h = 2 + (b - r) / delta
|
80
|
+
elsif (b == max)
|
81
|
+
h = 4 + (r - g) / delta
|
82
|
+
end
|
83
|
+
h *= 60.0
|
84
|
+
if (h < 0)
|
85
|
+
h += 360.0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
return [h,s,v]
|
89
|
+
end
|
90
|
+
def self.colorAverage(ca)
|
91
|
+
if ca.length==1
|
92
|
+
return ca.first
|
93
|
+
end
|
94
|
+
r=((ca.collect { |c| c[0] }).inject{ |sum, el| sum+el }).to_f / ca.size
|
95
|
+
g=((ca.collect { |c| c[1] }).inject{ |sum, el| sum+el }).to_f / ca.size
|
96
|
+
b=((ca.collect { |c| c[2] }).inject{ |sum, el| sum+el }).to_f / ca.size
|
97
|
+
return [r,g,b]
|
98
|
+
end
|
99
|
+
def self.colorDistance(c1,c2)
|
100
|
+
return Math.sqrt((c1[0]-c2[0])**2+(c1[1]-c2[1])**2+(c1[2]-c2[2])**2)
|
101
|
+
end
|
102
|
+
def self.colorUniqueness(c, ca)
|
103
|
+
return Pxlsrt::Colors.colorDistance(c, Pxlsrt::Colors.colorAverage(ca))
|
104
|
+
end
|
105
|
+
def self.pixelSort(list, how, reverse)
|
106
|
+
mhm=[]
|
107
|
+
case how.downcase
|
108
|
+
when "sum-rgb"
|
109
|
+
mhm= list.sort_by { |c| Pxlsrt::Colors.pxldex(c) }
|
110
|
+
when "red"
|
111
|
+
mhm= list.sort_by { |c| c[0] }
|
112
|
+
when "green"
|
113
|
+
mhm= list.sort_by { |c| c[1] }
|
114
|
+
when "blue"
|
115
|
+
mhm= list.sort_by { |c| c[2] }
|
116
|
+
when "hue"
|
117
|
+
mhm= list.sort_by { |c| Pxlsrt::Colors.rgb2hsb(c)[0] }
|
118
|
+
when "saturation"
|
119
|
+
mhm= list.sort_by { |c| Pxlsrt::Colors.rgb2hsb(c)[1] }
|
120
|
+
when "brightness"
|
121
|
+
mhm= list.sort_by { |c| Pxlsrt::Colors.rgb2hsb(c)[2] }
|
122
|
+
when "sum-hsb"
|
123
|
+
mhm= list.sort_by { |c| k=Pxlsrt::Colors.rgb2hsb(c); k[0]*100/360+k[1]+k[2] }
|
124
|
+
when "uniqueness"
|
125
|
+
avg=Pxlsrt::Colors.colorAverage(list)
|
126
|
+
mhm=list.sort_by { |c| Pxlsrt::Colors.colorUniqueness(c, [avg]) }
|
127
|
+
when "luma"
|
128
|
+
mhm=list.sort_by { |c| Pxlsrt::Colors.pxldex([c[0]*0.2126, c[1]*0.7152, c[2]*0.0722]) }
|
129
|
+
when "random"
|
130
|
+
mhm=list.shuffle
|
131
|
+
else
|
132
|
+
mhm= list.sort_by { |c| Pxlsrt::Colors.pxldex(c) }
|
133
|
+
end
|
134
|
+
if reverse == 0
|
135
|
+
return mhm
|
136
|
+
elsif reverse == 1
|
137
|
+
return mhm.reverse
|
138
|
+
else
|
139
|
+
return rand(0..1)==0 ? mhm : mhm.reverse
|
140
|
+
end
|
141
|
+
end
|
142
|
+
def self.getDiagonals(array, width, height)
|
143
|
+
dias={}
|
144
|
+
for x in (1-height)..(width-1)
|
145
|
+
z=[]
|
146
|
+
for y in 0..(height-1)
|
147
|
+
if (x+(width+1)*y).between?(width*y, (width*(y+1)-1))
|
148
|
+
z.push(array[(x+(width+1)*y)])
|
149
|
+
end
|
150
|
+
end
|
151
|
+
dias[x.to_s]=z
|
152
|
+
end
|
153
|
+
return dias
|
154
|
+
end
|
155
|
+
def self.fromDiagonals(obj, width)
|
156
|
+
ell=[]
|
157
|
+
for k in obj.keys
|
158
|
+
r=k.to_i
|
159
|
+
n=r < 0
|
160
|
+
if n
|
161
|
+
x=0
|
162
|
+
y=r.abs
|
163
|
+
else
|
164
|
+
x=r
|
165
|
+
y=0
|
166
|
+
end
|
167
|
+
ell[x+y*width]=obj[k].first
|
168
|
+
for v in 1..(obj[k].length-1)
|
169
|
+
x+=1
|
170
|
+
y+=1
|
171
|
+
ell[x+y*width]=obj[k][v]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
return ell
|
175
|
+
end
|
176
|
+
def self.arrayToRGB(a)
|
177
|
+
return ChunkyPNG::Color.rgb(a[0], a[1], a[2])
|
178
|
+
end
|
179
|
+
def self.sobelate(i, x,y)
|
180
|
+
return ChunkyPNG::Color.to_grayscale_bytes(i[x,y]).first
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pxlsrt
|
2
|
+
class Helpers
|
3
|
+
def self.contented(c)
|
4
|
+
return (c.class!=NilClass and ((defined? c)!="nil") and ((/(\S)/.match("#{c}"))!=nil))
|
5
|
+
end
|
6
|
+
def self.red(what)
|
7
|
+
return "\e[31m#{what}\e[0m"
|
8
|
+
end
|
9
|
+
def self.cyan(what)
|
10
|
+
return "\e[36m#{what}\e[0m"
|
11
|
+
end
|
12
|
+
def self.checkOptions(options, rules)
|
13
|
+
match=true
|
14
|
+
for o in options.keys
|
15
|
+
o_match=false
|
16
|
+
if rules[o].class==Array
|
17
|
+
if rules[o].include?(options[o])
|
18
|
+
o_match=true
|
19
|
+
else
|
20
|
+
for r in 0...rules[o].length
|
21
|
+
if rules[o][r].class==Hash
|
22
|
+
for n in rules[o][r][:class]
|
23
|
+
if n==options[o].class
|
24
|
+
o_match=match
|
25
|
+
break
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if o_match==true
|
30
|
+
break
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
match=(match and o_match)
|
36
|
+
if match==false
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return match
|
41
|
+
end
|
42
|
+
def self.error(what)
|
43
|
+
puts "#{Pxlsrt::Helpers.red("pxlsrt")} #{what}"
|
44
|
+
end
|
45
|
+
def self.verbose(what)
|
46
|
+
puts "#{Pxlsrt::Helpers.cyan("pxlsrt")} #{what}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/pxlsrt/smart.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'oily_png'
|
3
|
+
require 'json'
|
4
|
+
require 'pathname'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Pxlsrt
|
9
|
+
class Smart
|
10
|
+
def self.suite(inputFileName, outputFileName, trusted, o={})
|
11
|
+
kml=Pxlsrt::Smart.smart(inputFileName, trusted, o)
|
12
|
+
if Pxlsrt::Helpers.contented(kml)
|
13
|
+
kml.save(outputFileName)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def self.smart(input, trusted, o={})
|
17
|
+
startTime=Time.now
|
18
|
+
defOptions={
|
19
|
+
:reverse => "no",
|
20
|
+
:vertical => false,
|
21
|
+
:diagonal => false,
|
22
|
+
:smooth => false,
|
23
|
+
:method => "sum-rgb",
|
24
|
+
:verbose => false,
|
25
|
+
:absolute => false,
|
26
|
+
:threshold => 20,
|
27
|
+
:edge => 2
|
28
|
+
}
|
29
|
+
defRules={
|
30
|
+
:reverse => ["no", "reverse", "either"],
|
31
|
+
:vertical => [false, true],
|
32
|
+
:diagonal => [false, true],
|
33
|
+
:smooth => [false, true],
|
34
|
+
:method => ["sum-rgb", "red", "green", "blue", "sum-hsb", "hue", "saturation", "brightness", "uniqueness", "luma", "random"],
|
35
|
+
:verbose => [false, true],
|
36
|
+
:absolute => [false, true],
|
37
|
+
:threshold => [{:class => [Float, Fixnum]}],
|
38
|
+
:edge => [{:class => [Fixnum]}]
|
39
|
+
}
|
40
|
+
options=defOptions.merge(o)
|
41
|
+
if o.length==0 or trusted==true or (trusted==false and o.length!=0 and Pxlsrt::Helpers.checkOptions(options, defRules)!=false)
|
42
|
+
Pxlsrt::Helpers.verbose("Options are all good.") if options[:verbose]
|
43
|
+
if input.class==String
|
44
|
+
Pxlsrt::Helpers.verbose("Getting image from file...") if options[:verbose]
|
45
|
+
input=ChunkyPNG::Image.from_file(input)
|
46
|
+
elsif input.class!=String and input.class!=ChunkyPNG::Image
|
47
|
+
Pxlsrt::Helpers.error("Input is not a filename or ChunkyPNG::Image") if options[:verbose]
|
48
|
+
return
|
49
|
+
end
|
50
|
+
Pxlsrt::Helpers.verbose("Smart mode.") if options[:verbose]
|
51
|
+
case options[:reverse].downcase
|
52
|
+
when "reverse"
|
53
|
+
nre=1
|
54
|
+
when "either"
|
55
|
+
nre=-1
|
56
|
+
else
|
57
|
+
nre=0
|
58
|
+
end
|
59
|
+
img=input
|
60
|
+
w,h=img.width,img.height
|
61
|
+
sobel_x = [[-1,0,1], [-2,0,2], [-1,0,1]]
|
62
|
+
sobel_y = [[-1,-2,-1], [ 0, 0, 0], [ 1, 2, 1]]
|
63
|
+
edge = ChunkyPNG::Image.new(w, h, ChunkyPNG::Color::TRANSPARENT)
|
64
|
+
valued="start"
|
65
|
+
k=[]
|
66
|
+
Pxlsrt::Helpers.verbose("Getting Sobel values and colors for pixels...") if options[:verbose]
|
67
|
+
for xy in 0..(w*h-1)
|
68
|
+
x=xy % w
|
69
|
+
y=(xy/w).floor
|
70
|
+
if x!=0 and x!=(w-1) and y!=0 and y!=(h-1)
|
71
|
+
pixel_x=(sobel_x[0][0]*Pxlsrt::Colors.sobelate(img,x-1,y-1))+(sobel_x[0][1]*Pxlsrt::Colors.sobelate(img,x,y-1))+(sobel_x[0][2]*Pxlsrt::Colors.sobelate(img,x+1,y-1))+(sobel_x[1][0]*Pxlsrt::Colors.sobelate(img,x-1,y))+(sobel_x[1][1]*Pxlsrt::Colors.sobelate(img,x,y))+(sobel_x[1][2]*Pxlsrt::Colors.sobelate(img,x+1,y))+(sobel_x[2][0]*Pxlsrt::Colors.sobelate(img,x-1,y+1))+(sobel_x[2][1]*Pxlsrt::Colors.sobelate(img,x,y+1))+(sobel_x[2][2]*Pxlsrt::Colors.sobelate(img,x+1,y+1))
|
72
|
+
pixel_y=(sobel_y[0][0]*Pxlsrt::Colors.sobelate(img,x-1,y-1))+(sobel_y[0][1]*Pxlsrt::Colors.sobelate(img,x,y-1))+(sobel_y[0][2]*Pxlsrt::Colors.sobelate(img,x+1,y-1))+(sobel_y[1][0]*Pxlsrt::Colors.sobelate(img,x-1,y))+(sobel_y[1][1]*Pxlsrt::Colors.sobelate(img,x,y))+(sobel_y[1][2]*Pxlsrt::Colors.sobelate(img,x+1,y))+(sobel_y[2][0]*Pxlsrt::Colors.sobelate(img,x-1,y+1))+(sobel_y[2][1]*Pxlsrt::Colors.sobelate(img,x,y+1))+(sobel_y[2][2]*Pxlsrt::Colors.sobelate(img,x+1,y+1))
|
73
|
+
val = Math.sqrt(pixel_x * pixel_x + pixel_y * pixel_y).ceil
|
74
|
+
else
|
75
|
+
val = 2000000000
|
76
|
+
end
|
77
|
+
k.push({ "sobel" => val, "pixel" => [x, y], "color" => Pxlsrt::Colors.getRGB(img[x, y]) })
|
78
|
+
end
|
79
|
+
if options[:vertical]==true
|
80
|
+
Pxlsrt::Helpers.verbose("Rotating image for vertical mode...") if options[:verbose]
|
81
|
+
k=Pxlsrt::Colors.rotateImage(k,w,h,3)
|
82
|
+
w,h=h,w
|
83
|
+
end
|
84
|
+
if !options[:diagonal]
|
85
|
+
lines=Pxlsrt::Colors.imageRGBLines(k, w)
|
86
|
+
Pxlsrt::Helpers.verbose("Determining bands with a#{options[:absolute] ? "n absolute" : " relative"} threshold of #{options[:threshold]}...") if options[:verbose]
|
87
|
+
bands=Array.new()
|
88
|
+
for j in lines
|
89
|
+
slicing=true
|
90
|
+
pixel=0
|
91
|
+
m=Array.new()
|
92
|
+
while slicing do
|
93
|
+
n=Array.new
|
94
|
+
if m.length > 1
|
95
|
+
while m.last.length < options[:edge]
|
96
|
+
if m.length > 1
|
97
|
+
m[-2].concat(m[-1])
|
98
|
+
m.pop
|
99
|
+
else
|
100
|
+
break
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
bandWorking=true
|
105
|
+
while bandWorking do
|
106
|
+
n.push(j[pixel]["color"])
|
107
|
+
if (options[:absolute] ? (j[pixel+1]["sobel"]) : (j[pixel+1]["sobel"]-j[pixel]["sobel"])) > options[:threshold]
|
108
|
+
bandWorking=false
|
109
|
+
end
|
110
|
+
if (pixel+1)==(j.length-1)
|
111
|
+
n.push(j[pixel+1]["color"])
|
112
|
+
slicing=false
|
113
|
+
bandWorking=false
|
114
|
+
end
|
115
|
+
pixel+=1
|
116
|
+
end
|
117
|
+
m.push(n)
|
118
|
+
end
|
119
|
+
bands.concat(m)
|
120
|
+
end
|
121
|
+
Pxlsrt::Helpers.verbose("Pixel sorting using method '#{options[:method]}'...") if options[:verbose]
|
122
|
+
image=[]
|
123
|
+
if options[:smooth]
|
124
|
+
for band in bands
|
125
|
+
u=band.group_by {|x| x}
|
126
|
+
image.concat(Pxlsrt::Colors.pixelSort(u.keys, options[:method], nre).map { |x| u[x] }.flatten(1))
|
127
|
+
end
|
128
|
+
else
|
129
|
+
for band in bands
|
130
|
+
image.concat(Pxlsrt::Colors.pixelSort(band, options[:method], nre))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else
|
134
|
+
Pxlsrt::Helpers.verbose("Determining diagonals...") if options[:verbose]
|
135
|
+
dia=Pxlsrt::Colors.getDiagonals(k,w,h)
|
136
|
+
Pxlsrt::Helpers.verbose("Determining bands with a#{options[:absolute] ? "n absolute" : " relative"} threshold of #{options[:threshold]}...") if options[:verbose]
|
137
|
+
for j in dia.keys
|
138
|
+
bands=[]
|
139
|
+
if dia[j].length>1
|
140
|
+
slicing=true
|
141
|
+
pixel=0
|
142
|
+
m=Array.new()
|
143
|
+
while slicing do
|
144
|
+
n=Array.new
|
145
|
+
if m.length > 1
|
146
|
+
while m.last.length < options[:edge]
|
147
|
+
if m.length > 1
|
148
|
+
m[-2].concat(m[-1])
|
149
|
+
m.pop
|
150
|
+
else
|
151
|
+
break
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
bandWorking=true
|
156
|
+
while bandWorking do
|
157
|
+
n.push(dia[j][pixel]["color"])
|
158
|
+
if (options[:absolute] ? (dia[j][pixel+1]["sobel"]) : (dia[j][pixel+1]["sobel"]-dia[j][pixel]["sobel"])) > options[:threshold]
|
159
|
+
bandWorking=false
|
160
|
+
end
|
161
|
+
if (pixel+1)==(dia[j].length-1)
|
162
|
+
n.push(dia[j][pixel+1]["color"])
|
163
|
+
slicing=false
|
164
|
+
bandWorking=false
|
165
|
+
end
|
166
|
+
pixel+=1
|
167
|
+
end
|
168
|
+
m.push(n)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
m=[[dia[j].first["color"]]]
|
172
|
+
end
|
173
|
+
dia[j]=bands.concat(m)
|
174
|
+
end
|
175
|
+
Pxlsrt::Helpers.verbose("Pixel sorting using method '#{options[:method]}'...") if options[:verbose]
|
176
|
+
for j in dia.keys
|
177
|
+
ell=[]
|
178
|
+
if options[:smooth]
|
179
|
+
for band in dia[j]
|
180
|
+
u=band.group_by {|x| x}
|
181
|
+
ell.concat(Pxlsrt::Colors.pixelSort(u.keys, options[:method], nre).map { |x| u[x] }.flatten(1))
|
182
|
+
end
|
183
|
+
else
|
184
|
+
for band in dia[j]
|
185
|
+
ell.concat(Pxlsrt::Colors.pixelSort(band, options[:method], nre))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
dia[j]=ell
|
189
|
+
end
|
190
|
+
Pxlsrt::Helpers.verbose("Setting diagonals back to standard lines...") if options[:verbose]
|
191
|
+
image=Pxlsrt::Colors.fromDiagonals(dia,w)
|
192
|
+
end
|
193
|
+
if options[:vertical]==true
|
194
|
+
Pxlsrt::Helpers.verbose("Rotating back (because of vertical mode).") if options[:verbose]
|
195
|
+
image=Pxlsrt::Colors.rotateImage(image,w,h,1)
|
196
|
+
w,h=h,w
|
197
|
+
end
|
198
|
+
Pxlsrt::Helpers.verbose("Giving pixels new RGB values...") if options[:verbose]
|
199
|
+
for px in 0..(w*h-1)
|
200
|
+
edge[px % w, (px/w).floor]=Pxlsrt::Colors.arrayToRGB(image[px])
|
201
|
+
end
|
202
|
+
endTime=Time.now
|
203
|
+
timeElapsed=endTime-startTime
|
204
|
+
if timeElapsed < 60
|
205
|
+
Pxlsrt::Helpers.verbose("Took #{timeElapsed.round(4)} second#{ timeElapsed.round(4)!=1.0 ? "s" : "" }.") if options[:verbose]
|
206
|
+
else
|
207
|
+
minutes=(timeElapsed/60).floor
|
208
|
+
seconds=(timeElapsed % 60).round(4)
|
209
|
+
Pxlsrt::Helpers.verbose("Took #{minutes} minute#{ minutes!=1 ? "s" : "" } and #{seconds} second#{ seconds!=1.0 ? "s" : "" }.") if options[:verbose]
|
210
|
+
end
|
211
|
+
Pxlsrt::Helpers.verbose("Returning ChunkyPNG::Image...") if options[:verbose]
|
212
|
+
return edge
|
213
|
+
else
|
214
|
+
Pxlsrt::Helpers.error("Options specified do not follow the correct format.") if options[:verbose]
|
215
|
+
return
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/lib/pxlsrt.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pxlsrt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- EVA-01
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oily_png
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.18'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.18'
|
69
|
+
description: Pixel sort PNG files with ease!
|
70
|
+
email:
|
71
|
+
- j.bruno.che@gmail.com
|
72
|
+
executables:
|
73
|
+
- pxlsrt
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- bin/pxlsrt
|
78
|
+
- lib/pxlsrt.rb
|
79
|
+
- lib/pxlsrt/brute.rb
|
80
|
+
- lib/pxlsrt/colors.rb
|
81
|
+
- lib/pxlsrt/helpers.rb
|
82
|
+
- lib/pxlsrt/smart.rb
|
83
|
+
- lib/pxlsrt/version.rb
|
84
|
+
homepage: https://github.com/EVA-01/pxlsrt
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.2.2
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Pixel sort PNG files.
|
108
|
+
test_files: []
|