qttk 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +36 -0
- data/README.markdown +131 -0
- data/Rakefile +17 -0
- data/TODO.markdown +236 -0
- data/bin/qt +7 -0
- data/etc/gutenprint/gp-tool.rb +56 -0
- data/etc/gutenprint/gutenprint-filter.c +400 -0
- data/etc/gutenprint/gutenprint.rb +86 -0
- data/etc/gutenprint/stp-test +326 -0
- data/etc/images/3551599565_db282cf840_o.jpg +0 -0
- data/etc/images/4843122063_d582c569e9_o.jpg +0 -0
- data/etc/images/4843128953_83c1770907_o.jpg +0 -0
- data/lib/quadtone.rb +56 -0
- data/lib/quadtone/cgats.rb +137 -0
- data/lib/quadtone/cluster_calculator.rb +81 -0
- data/lib/quadtone/color.rb +83 -0
- data/lib/quadtone/color/cmyk.rb +112 -0
- data/lib/quadtone/color/device_n.rb +23 -0
- data/lib/quadtone/color/gray.rb +46 -0
- data/lib/quadtone/color/lab.rb +150 -0
- data/lib/quadtone/color/qtr.rb +71 -0
- data/lib/quadtone/color/rgb.rb +71 -0
- data/lib/quadtone/color/xyz.rb +80 -0
- data/lib/quadtone/curve.rb +138 -0
- data/lib/quadtone/curve_set.rb +196 -0
- data/lib/quadtone/descendants.rb +9 -0
- data/lib/quadtone/environment.rb +5 -0
- data/lib/quadtone/extensions/math.rb +11 -0
- data/lib/quadtone/extensions/pathname3.rb +11 -0
- data/lib/quadtone/printer.rb +106 -0
- data/lib/quadtone/profile.rb +217 -0
- data/lib/quadtone/quad_file.rb +59 -0
- data/lib/quadtone/renderer.rb +139 -0
- data/lib/quadtone/run.rb +10 -0
- data/lib/quadtone/sample.rb +32 -0
- data/lib/quadtone/separator.rb +36 -0
- data/lib/quadtone/target.rb +277 -0
- data/lib/quadtone/tool.rb +61 -0
- data/lib/quadtone/tools/add_printer.rb +73 -0
- data/lib/quadtone/tools/characterize.rb +43 -0
- data/lib/quadtone/tools/chart.rb +31 -0
- data/lib/quadtone/tools/check.rb +16 -0
- data/lib/quadtone/tools/dir.rb +15 -0
- data/lib/quadtone/tools/edit.rb +23 -0
- data/lib/quadtone/tools/init.rb +82 -0
- data/lib/quadtone/tools/install.rb +15 -0
- data/lib/quadtone/tools/linearize.rb +28 -0
- data/lib/quadtone/tools/list.rb +19 -0
- data/lib/quadtone/tools/print.rb +38 -0
- data/lib/quadtone/tools/printer_options.rb +40 -0
- data/lib/quadtone/tools/rename.rb +17 -0
- data/lib/quadtone/tools/render.rb +43 -0
- data/lib/quadtone/tools/rewrite.rb +15 -0
- data/lib/quadtone/tools/separate.rb +71 -0
- data/lib/quadtone/tools/show.rb +15 -0
- data/lib/quadtone/tools/test.rb +26 -0
- data/qttk.gemspec +34 -0
- metadata +215 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
module Quadtone
|
2
|
+
|
3
|
+
class CGATS
|
4
|
+
|
5
|
+
attr_accessor :sections
|
6
|
+
|
7
|
+
def self.new_from_file(file)
|
8
|
+
cgats = new
|
9
|
+
section_index = 0
|
10
|
+
state = :header
|
11
|
+
Pathname.new(file).readlines.each do |line|
|
12
|
+
line.chomp!
|
13
|
+
line.sub!(/#.*/, '')
|
14
|
+
line.strip!
|
15
|
+
next if line.empty?
|
16
|
+
section = cgats.sections[section_index] || cgats.add_section
|
17
|
+
case state
|
18
|
+
when :header
|
19
|
+
case line
|
20
|
+
when 'BEGIN_DATA_FORMAT'
|
21
|
+
state = :data_format
|
22
|
+
when 'BEGIN_DATA'
|
23
|
+
state = :data
|
24
|
+
else
|
25
|
+
key, value = line.split(/\s+/, 2)
|
26
|
+
if section.header[key]
|
27
|
+
if !section.header[key].kind_of?(Array)
|
28
|
+
section.header[key] = [section.header[key]]
|
29
|
+
end
|
30
|
+
section.header[key] << value
|
31
|
+
else
|
32
|
+
section.header[key] = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
when :data_format
|
36
|
+
case line
|
37
|
+
when 'END_DATA_FORMAT'
|
38
|
+
state = :header
|
39
|
+
else
|
40
|
+
line.split(/\s+/).each { |f| section.data_fields << f }
|
41
|
+
end
|
42
|
+
when :data
|
43
|
+
case line
|
44
|
+
when 'END_DATA'
|
45
|
+
# Emission data (BEGIN_DATA_EMISSION) may come after here, but we don't handle it.
|
46
|
+
section_index += 1
|
47
|
+
state = :header
|
48
|
+
else
|
49
|
+
values = line.split(/\s+/).map do |v|
|
50
|
+
case v
|
51
|
+
when /^-?\d+$/
|
52
|
+
v.to_i
|
53
|
+
when /^-?\d+\.\d+$/
|
54
|
+
v.to_f
|
55
|
+
else
|
56
|
+
v.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
set = {}
|
60
|
+
values.each_with_index do |value, i|
|
61
|
+
set[section.data_fields[i]] = value
|
62
|
+
end
|
63
|
+
section.data << set
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
cgats
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@sections = []
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_section
|
75
|
+
@sections << Section.new
|
76
|
+
@sections[-1]
|
77
|
+
end
|
78
|
+
|
79
|
+
def write(io)
|
80
|
+
@sections.each do |section|
|
81
|
+
section.write(io)
|
82
|
+
io.puts
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
class Section
|
88
|
+
|
89
|
+
attr_accessor :header
|
90
|
+
attr_accessor :data
|
91
|
+
attr_accessor :data_fields
|
92
|
+
|
93
|
+
def initialize
|
94
|
+
@header = {}
|
95
|
+
@data = []
|
96
|
+
@data_fields = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def <<(set)
|
100
|
+
@data << set
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(io)
|
104
|
+
# header
|
105
|
+
@header.each { |k, v| io.puts k.to_s + (v ? " \"#{v}\"" : '') }
|
106
|
+
# data format
|
107
|
+
io.puts
|
108
|
+
io.puts "NUMBER_OF_FIELDS #{@data_fields.length}"
|
109
|
+
io.puts 'BEGIN_DATA_FORMAT'
|
110
|
+
io.puts @data_fields.join(' ')
|
111
|
+
io.puts 'END_DATA_FORMAT'
|
112
|
+
# data
|
113
|
+
io.puts
|
114
|
+
io.puts "NUMBER_OF_SETS #{@data.length}"
|
115
|
+
io.puts 'BEGIN_DATA'
|
116
|
+
@data.each do |set|
|
117
|
+
fields = @data_fields.map do |f|
|
118
|
+
case (d = set[f])
|
119
|
+
when Float
|
120
|
+
'%.05f' % d
|
121
|
+
when String
|
122
|
+
'"' + d + '"'
|
123
|
+
else
|
124
|
+
d
|
125
|
+
end
|
126
|
+
end
|
127
|
+
io.puts fields.join(' ')
|
128
|
+
end
|
129
|
+
io.puts 'END_DATA'
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# after:
|
2
|
+
# http://colinfdrake.com/2011/05/28/clustering-in-ruby.html
|
3
|
+
# http://m635j520.blogspot.com/2013/02/implementing-k-means-clustering-in-ruby.html
|
4
|
+
|
5
|
+
class ClusterCalculator
|
6
|
+
|
7
|
+
class Cluster
|
8
|
+
|
9
|
+
attr_accessor :center
|
10
|
+
attr_accessor :samples
|
11
|
+
attr_accessor :moved
|
12
|
+
|
13
|
+
def initialize(center)
|
14
|
+
@center = center
|
15
|
+
@samples = []
|
16
|
+
@moved = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_sample(sample)
|
20
|
+
@samples << sample
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear_samples
|
24
|
+
@samples = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def distance_to(sample)
|
28
|
+
@center.delta_e(sample.output)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_center(delta=0.001)
|
32
|
+
@moved = false
|
33
|
+
average, error = Color::Lab.average(@samples.map(&:output))
|
34
|
+
unless average.delta_e(@center) < delta
|
35
|
+
@center = average
|
36
|
+
@moved = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
@samples.length
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_accessor :samples
|
47
|
+
attr_accessor :max_clusters
|
48
|
+
attr_accessor :delta
|
49
|
+
attr_accessor :clusters
|
50
|
+
|
51
|
+
def initialize(params={})
|
52
|
+
@delta = 0.001
|
53
|
+
params.each { |k, v| send("#{k}=", v) }
|
54
|
+
raise "Must specify samples" unless @samples
|
55
|
+
raise "Must specify max_clusters" unless @max_clusters
|
56
|
+
@max_clusters = @samples.length if @max_clusters > @samples.length
|
57
|
+
end
|
58
|
+
|
59
|
+
def cluster!
|
60
|
+
@clusters = @max_clusters.times.map { Cluster.new(@samples.sample.output) }
|
61
|
+
while @clusters.any?(&:moved)
|
62
|
+
@clusters.each(&:clear_samples)
|
63
|
+
@samples.each do |sample|
|
64
|
+
shortest = Float::INFINITY
|
65
|
+
cluster_found = nil
|
66
|
+
@clusters.each do |cluster|
|
67
|
+
distance = cluster.distance_to(sample)
|
68
|
+
if distance < shortest
|
69
|
+
cluster_found = cluster
|
70
|
+
shortest = distance
|
71
|
+
end
|
72
|
+
end
|
73
|
+
cluster_found.add_sample(sample) if cluster_found
|
74
|
+
end
|
75
|
+
@clusters.delete_if { |c| c.size == 0 }
|
76
|
+
@clusters.each { |c| c.update_center(@delta) }
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
include Math
|
6
|
+
|
7
|
+
attr_accessor :components
|
8
|
+
|
9
|
+
def self.component_names
|
10
|
+
raise NotImplementedError, "\#component_names not implemented in #{self}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.cgats_fields
|
14
|
+
raise NotImplementedError, "\#cgats_fields not implemented in #{self}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.num_components
|
18
|
+
component_names.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.colorspace_name
|
22
|
+
self.to_s.split('::').last.downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_cgats(set)
|
26
|
+
new(set.values_at(*cgats_fields))
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.average(colors)
|
30
|
+
avg_components = []
|
31
|
+
errors = []
|
32
|
+
component_names.each_with_index do |comp, i|
|
33
|
+
avg_components << colors.map { |c| c.components[i] }.mean
|
34
|
+
errors << colors.map { |c| c.components[i] }.standard_deviation
|
35
|
+
end
|
36
|
+
[new(avg_components), errors.max]
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(arg)
|
40
|
+
components = case arg
|
41
|
+
when String
|
42
|
+
arg =~ /^(\w+)\((.+)\)$/ or raise "Can't initialize #{self.class}: bad color string: #{arg.inspect}"
|
43
|
+
raise "Expected #{self.class.colorspace_name.inspect} but got #{$1.inspect}" if $1.downcase != self.class.colorspace_name
|
44
|
+
$2.split(/,\s+/).map(&:to_f)
|
45
|
+
when Hash
|
46
|
+
self.class.component_names.map { |n| arg[n] }
|
47
|
+
when Array
|
48
|
+
arg.map(&:to_f)
|
49
|
+
else
|
50
|
+
raise "Can't initialize #{self.class}: unknown object: #{arg.inspect}"
|
51
|
+
end
|
52
|
+
raise "Can't initialize #{self.class}: too many components specified: #{components.inspect}" if components.length > self.class.num_components
|
53
|
+
@components = [0] * self.class.num_components
|
54
|
+
components.each_with_index { |n, i| @components[i] = n if n }
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"#{self.class.colorspace_name}(#{@components.map { |n| '%3.1f' % n }.join(', ')})"
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_str
|
62
|
+
to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_cgats
|
66
|
+
@components
|
67
|
+
end
|
68
|
+
|
69
|
+
def hash
|
70
|
+
@components.hash
|
71
|
+
end
|
72
|
+
|
73
|
+
def eql?(other)
|
74
|
+
@components == other.components
|
75
|
+
end
|
76
|
+
|
77
|
+
def <=>(other)
|
78
|
+
@components <=> other.components
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class CMYK < Base
|
4
|
+
|
5
|
+
def self.component_names
|
6
|
+
[:c, :m, :y, :k, :lc, :lm, :lk, :llk]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.cgats_fields
|
10
|
+
%w{CMYKcmk1k_C CMYKcmk1k_M CMYKcmk1k_Y CMYKcmk1k_K
|
11
|
+
CMYKcmk1k_c CMYKcmk1k_m CMYKcmk1k_k CMYKcmk1k_1k}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cgats_color_rep
|
15
|
+
'CMYKcmk1k'
|
16
|
+
end
|
17
|
+
|
18
|
+
def c
|
19
|
+
@components[0]
|
20
|
+
end
|
21
|
+
|
22
|
+
def m
|
23
|
+
@components[1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def y
|
27
|
+
@components[2]
|
28
|
+
end
|
29
|
+
|
30
|
+
def k
|
31
|
+
@components[3]
|
32
|
+
end
|
33
|
+
|
34
|
+
def lc
|
35
|
+
@components[4]
|
36
|
+
end
|
37
|
+
|
38
|
+
def lm
|
39
|
+
@components[5]
|
40
|
+
end
|
41
|
+
|
42
|
+
def lk
|
43
|
+
@components[6]
|
44
|
+
end
|
45
|
+
|
46
|
+
def llk
|
47
|
+
@components[7]
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_cgats
|
51
|
+
{
|
52
|
+
'CMYKcmk1k_C' => c,
|
53
|
+
'CMYKcmk1k_M' => m,
|
54
|
+
'CMYKcmk1k_Y' => y,
|
55
|
+
'CMYKcmk1k_K' => k,
|
56
|
+
'CMYKcmk1k_c' => lc,
|
57
|
+
'CMYKcmk1k_m' => lm,
|
58
|
+
'CMYKcmk1k_k' => lk,
|
59
|
+
'CMYKcmk1k_1k' => llk,
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_cmyk
|
64
|
+
# estimates for light & light-light inks
|
65
|
+
l_factor = 0.5
|
66
|
+
ll_factor = 0.25
|
67
|
+
|
68
|
+
# first adjust for light inks
|
69
|
+
c0 = c + (lc * l_factor)
|
70
|
+
m0 = m + (lm * l_factor)
|
71
|
+
y0 = y
|
72
|
+
k0 = k + (lk * l_factor) + (llk * ll_factor)
|
73
|
+
|
74
|
+
Color::CMYK.new([c0, m0, y0, k0])
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_cmy
|
78
|
+
# after http://www.easyrgb.com/index.php?X=MATH&H=14#text14
|
79
|
+
|
80
|
+
c0, m0, y0, k0 = *to_cmyk
|
81
|
+
c0 /= 100.0
|
82
|
+
m0 /= 100.0
|
83
|
+
y0 /= 100.0
|
84
|
+
k0 /= 100.0
|
85
|
+
|
86
|
+
c0 = (c0 * (1 - k0)) + k0
|
87
|
+
m0 = (m0 * (1 - k0)) + k0
|
88
|
+
y0 = (y0 * (1 - k0)) + k0
|
89
|
+
|
90
|
+
Color::CMYK.new([c0 * 100, m0 * 100, y0 * 100])
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_rgb
|
94
|
+
cmy = to_cmy
|
95
|
+
Color::RGB.new([1 - (cmy.c / 100), 1 - (cmy.m / 100), 1 - (cmy.y / 100)])
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_lab
|
99
|
+
to_xyz.to_lab
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_xyz
|
103
|
+
to_rgb.to_xyz
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_a
|
107
|
+
@components
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class DeviceN < Base
|
4
|
+
|
5
|
+
Components = (0..7).to_a
|
6
|
+
|
7
|
+
def self.component_names
|
8
|
+
Components
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.cgats_fields
|
12
|
+
Components.map do |i|
|
13
|
+
"DEVICE_#{i}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_gray
|
18
|
+
Color::Gray.new(@components.find { |c| c != 0 } || 0)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class Gray < Base
|
4
|
+
|
5
|
+
def self.component_names
|
6
|
+
[:k]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.cgats_fields
|
10
|
+
%w{GRAY_K}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.cgats_color_rep
|
14
|
+
'K'
|
15
|
+
end
|
16
|
+
|
17
|
+
def k
|
18
|
+
@components[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
k / 100.0
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_cgats
|
26
|
+
{
|
27
|
+
'GRAY_K' => k,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_rgb
|
32
|
+
n = 1 - value
|
33
|
+
Color::RGB.new([n, n, n])
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_lab
|
37
|
+
Color::Lab.new(l: 100 - k, a: 0, b: 0)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_xyz
|
41
|
+
to_lab.to_xyz
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|