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,150 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class Lab < Base
|
4
|
+
|
5
|
+
DeltaEMethods = [
|
6
|
+
:density,
|
7
|
+
:cie76,
|
8
|
+
:cie94,
|
9
|
+
:cmclc
|
10
|
+
]
|
11
|
+
|
12
|
+
def self.component_names
|
13
|
+
[:l, :a, :b]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.cgats_fields
|
17
|
+
%w{LAB_L LAB_A LAB_B}
|
18
|
+
end
|
19
|
+
|
20
|
+
def l
|
21
|
+
@components[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def a
|
25
|
+
@components[1]
|
26
|
+
end
|
27
|
+
|
28
|
+
def b
|
29
|
+
@components[2]
|
30
|
+
end
|
31
|
+
|
32
|
+
def value
|
33
|
+
1 - (l / 100.0)
|
34
|
+
end
|
35
|
+
|
36
|
+
def chroma
|
37
|
+
# http://www.brucelindbloom.com/Eqn_Lab_to_LCH.html
|
38
|
+
sqrt((a * a) + (b * b))
|
39
|
+
end
|
40
|
+
|
41
|
+
def hue
|
42
|
+
# http://www.brucelindbloom.com/Eqn_Lab_to_LCH.html
|
43
|
+
if a == 0 && b == 0
|
44
|
+
0
|
45
|
+
else
|
46
|
+
rad2deg(atan2(b, a)) % 360
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def delta_e(other, method=:cmclc)
|
51
|
+
# http://en.wikipedia.org/wiki/Color_difference
|
52
|
+
# http://www.brucelindbloom.com/iPhone/ColorDiff.html
|
53
|
+
l1, a1, b1 = self.l, self.a, self.b
|
54
|
+
l2, a2, b2 = other.l, other.a, other.b
|
55
|
+
dl = l2 - l1
|
56
|
+
da = a1 - a2
|
57
|
+
db = b1 - b2
|
58
|
+
if method == :cie94 || method == :cmclc
|
59
|
+
c1, c2 = self.chroma, other.chroma
|
60
|
+
h1, h2 = self.hue, other.hue
|
61
|
+
dc = c1 - c2
|
62
|
+
dh2 = da**2 + db**2 - dc**2
|
63
|
+
return Float::NAN if dh2 < 0
|
64
|
+
dh = sqrt(dh2)
|
65
|
+
end
|
66
|
+
case method
|
67
|
+
when :density
|
68
|
+
dl.abs
|
69
|
+
when :cie76
|
70
|
+
sqrt(dl**2 + da**2 + db**2)
|
71
|
+
when :cie94
|
72
|
+
kl, k1, k2 = 1, 0.045, 0.015
|
73
|
+
sqrt(
|
74
|
+
(dl / kl)**2 +
|
75
|
+
(dc / (1 + k1*c1))**2 +
|
76
|
+
(dh / (1 + k2*c2)**2)
|
77
|
+
)
|
78
|
+
when :cmclc
|
79
|
+
l, c = 2, 1
|
80
|
+
sl = (l1 < 16) ?
|
81
|
+
0.511 :
|
82
|
+
0.040975 * l1 / (1 + 0.01765 * l1)
|
83
|
+
sc = 0.0638 * c1 / (1 + 0.0131 * c1) + 0.638
|
84
|
+
f = sqrt(
|
85
|
+
(c1 ** 4) / ((c1 ** 4) + 1900)
|
86
|
+
)
|
87
|
+
t = (h1 >= 164 && h1 <= 345) ?
|
88
|
+
0.56 + (0.2 * cos(deg2rad(h1 + 168))).abs :
|
89
|
+
0.36 + (0.4 * cos(deg2rad(h1 + 35))).abs
|
90
|
+
sh = sc * ((f * t) + 1 - f)
|
91
|
+
sqrt(
|
92
|
+
(dl / (l * sl)) ** 2 +
|
93
|
+
(dc / (c * sc)) ** 2 +
|
94
|
+
(dh / sh) ** 2
|
95
|
+
)
|
96
|
+
else
|
97
|
+
raise "Unknown deltaE method: #{method.inspect}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_xyz
|
102
|
+
# after http://www.easyrgb.com/index.php?X=MATH&H=08#text8
|
103
|
+
|
104
|
+
y = (l + 16) / 116.0
|
105
|
+
x = (a / 500.0) + y
|
106
|
+
z = y - (b / 200.0)
|
107
|
+
|
108
|
+
x, y, z = [x, y, z].map do |n|
|
109
|
+
if (n3 = n**3) > 0.008856
|
110
|
+
n3
|
111
|
+
else
|
112
|
+
(n - 16 / 116.0) / 7.787
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
ref = Color::XYZ.standard_reference
|
117
|
+
x *= ref.x
|
118
|
+
y *= ref.y
|
119
|
+
z *= ref.z
|
120
|
+
|
121
|
+
Color::XYZ.new([x, y, z])
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_rgb
|
125
|
+
to_xyz.to_rgb
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
if $0 == __FILE__
|
133
|
+
|
134
|
+
pairs = [
|
135
|
+
[Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,0,0)],
|
136
|
+
[Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(51,0,0)],
|
137
|
+
[Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,0,1)],
|
138
|
+
[Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,1,0)],
|
139
|
+
[Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,1,1)],
|
140
|
+
]
|
141
|
+
|
142
|
+
pairs.each do |pair|
|
143
|
+
l1, l2 = *pair
|
144
|
+
puts "#{l1.inspect} ~ #{l2.inspect}"
|
145
|
+
[:density, :cie76, :cie94, :cmclc].each do |method|
|
146
|
+
puts "\t" + "#{method}: %.4f" % l1.delta_e(l2, method)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# "Color" in QTR calibration mode.
|
2
|
+
|
3
|
+
module Color
|
4
|
+
|
5
|
+
class QTR < Base
|
6
|
+
|
7
|
+
=begin
|
8
|
+
|
9
|
+
QTR channel map (in calibration mode):
|
10
|
+
|
11
|
+
R: inverse bitmask for channels
|
12
|
+
|
13
|
+
7: K = 127 (01111111)
|
14
|
+
6: C = 191 (10111111)
|
15
|
+
5: M = 223 (11011111)
|
16
|
+
4: Y = 239 (11101111)
|
17
|
+
3: LC = 247 (11110111)
|
18
|
+
2: LM = 251 (11111011)
|
19
|
+
1: LK = 253 (11111101)
|
20
|
+
0: LLK = 254 (11111110)
|
21
|
+
|
22
|
+
G: value (0-255)
|
23
|
+
|
24
|
+
B: unused -- should always be 255
|
25
|
+
|
26
|
+
For background, use R=127 / G=255 / B=255
|
27
|
+
=end
|
28
|
+
|
29
|
+
Channels = [:llk, :lk, :lm, :lc, :y, :m, :c, :k]
|
30
|
+
|
31
|
+
def self.component_names
|
32
|
+
[:channel, :value]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.cgats_fields
|
36
|
+
%w{QTR_CHANNEL QTR_VALUE}
|
37
|
+
end
|
38
|
+
|
39
|
+
def channel
|
40
|
+
@components[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def channel_num
|
44
|
+
Channels.index(channel)
|
45
|
+
end
|
46
|
+
|
47
|
+
def value
|
48
|
+
@components[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_rgb
|
52
|
+
Color::RGB.new(
|
53
|
+
r: (255 - (1 << channel_num)) / 255.0,
|
54
|
+
g: (1 - value) / 100,
|
55
|
+
b: 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_gray
|
59
|
+
Color::Gray.new(k: value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_cgats
|
63
|
+
{
|
64
|
+
'QTR_CHANNEL' => channel,
|
65
|
+
'QTR_VALUE' => value,
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class RGB < Base
|
4
|
+
|
5
|
+
def self.component_names
|
6
|
+
[:r, :g, :b]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.cgats_fields
|
10
|
+
%w{RGB_R RGB_G RGB_B}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_cgats(set)
|
14
|
+
new(*set.values_at(*cgats_fields).map { |n| n / 100.0 })
|
15
|
+
end
|
16
|
+
|
17
|
+
def r
|
18
|
+
@components[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
def g
|
22
|
+
@components[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def b
|
26
|
+
@components[2]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_cgats
|
30
|
+
{
|
31
|
+
'RGB_R' => r * 100,
|
32
|
+
'RGB_G' => g * 100,
|
33
|
+
'RGB_B' => b * 100,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_xyz
|
38
|
+
# after http://www.easyrgb.com/index.php?X=MATH&H=02#text2
|
39
|
+
|
40
|
+
r0, g0, b0 = [r, g, b].map do |n|
|
41
|
+
if n > 0.04045
|
42
|
+
((n + 0.055) / 1.055) ** 2.4
|
43
|
+
else
|
44
|
+
n / 12.92
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
r0 *= 100
|
49
|
+
g0 *= 100
|
50
|
+
b0 *= 100
|
51
|
+
|
52
|
+
# Observer. = 2°, Illuminant = D65
|
53
|
+
|
54
|
+
x = (r0 * 0.4124) + (g0 * 0.3576) + (b0 * 0.1805)
|
55
|
+
y = (r0 * 0.2126) + (g0 * 0.7152) + (b0 * 0.0722)
|
56
|
+
z = (r0 * 0.0193) + (g0 * 0.1192) + (b0 * 0.9505)
|
57
|
+
|
58
|
+
Color::XYZ.new([x, y, z])
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
[r, g, b]
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_pixel
|
66
|
+
Magick::Pixel.new(*to_a.map { |n| n * Magick::QuantumRange })
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Color
|
2
|
+
|
3
|
+
class XYZ < Base
|
4
|
+
|
5
|
+
# Observer= 2°, Illuminant= D65
|
6
|
+
def self.standard_reference
|
7
|
+
@@reference ||= new([95.047, 100.000, 108.883])
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.component_names
|
11
|
+
[:x, :y, :z]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cgats_fields
|
15
|
+
%w{XYZ_X XYZ_Y XYZ_Z}
|
16
|
+
end
|
17
|
+
|
18
|
+
def x
|
19
|
+
@components[0]
|
20
|
+
end
|
21
|
+
|
22
|
+
def y
|
23
|
+
@components[1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def z
|
27
|
+
@components[2]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_lab
|
31
|
+
# http://www.easyrgb.com/index.php?X=MATH&H=07#text7
|
32
|
+
|
33
|
+
ref = self.class.standard_reference
|
34
|
+
|
35
|
+
x = self.x / ref.x
|
36
|
+
y = self.y / ref.y
|
37
|
+
z = self.z / ref.z
|
38
|
+
|
39
|
+
x, y, z = [x, y, z].map do |n|
|
40
|
+
if n > 0.008856
|
41
|
+
n ** (1.0 / 3)
|
42
|
+
else
|
43
|
+
(7.787 * n) + (16 / 116)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
l = (116 * y) - 16
|
48
|
+
a = 500 * (x - y)
|
49
|
+
b = 200 * (y - z)
|
50
|
+
|
51
|
+
Color::Lab.new([l, a, b])
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_rgb
|
55
|
+
rgb = [
|
56
|
+
(x * 3.2406) + (y * -1.5372) + (z * -0.4986),
|
57
|
+
(x * -0.9689) + (y * 1.8758) + (z * 0.0415),
|
58
|
+
(x * 0.0557) + (y * -0.2040) + (z * 1.0570),
|
59
|
+
]
|
60
|
+
rgb = rgb.map do |n|
|
61
|
+
if n > 0.0031308
|
62
|
+
1.055 * (n ** (1 / 2.4)) - 0.055
|
63
|
+
else
|
64
|
+
12.92 * rgb[0]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
Color::RGB.new(rgb)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_cgats
|
71
|
+
{
|
72
|
+
'XYZ_X' => x,
|
73
|
+
'XYZ_Y' => y,
|
74
|
+
'XYZ_Z' => z,
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Quadtone
|
2
|
+
|
3
|
+
class Curve
|
4
|
+
|
5
|
+
DeltaETolerance = 1.0
|
6
|
+
DeltaEMethod = :density
|
7
|
+
|
8
|
+
attr_accessor :channel
|
9
|
+
attr_accessor :samples
|
10
|
+
|
11
|
+
def initialize(params={})
|
12
|
+
params.each { |key, value| send("#{key}=", value) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def samples=(samples)
|
16
|
+
@samples = samples.sort_by(&:input_value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def output_for_input(input)
|
20
|
+
@output_spliner ||= Spliner::Spliner.new(@samples.map(&:input_value), @samples.map(&:output_value))
|
21
|
+
@output_spliner[input]
|
22
|
+
end
|
23
|
+
|
24
|
+
def input_for_output(output)
|
25
|
+
@input_spliner ||= Spliner::Spliner.new(@samples.map(&:output_value), @samples.map(&:input_value))
|
26
|
+
@input_spliner[output]
|
27
|
+
end
|
28
|
+
|
29
|
+
def num_samples
|
30
|
+
@samples.length
|
31
|
+
end
|
32
|
+
|
33
|
+
def grayscale(steps)
|
34
|
+
spliner = Spliner::Spliner.new(Hash[ @samples.map { |s| [s.input.value, s.output.l] } ])
|
35
|
+
scale = (0 .. 1).step(1.0 / (steps - 1))
|
36
|
+
spliner[scale].map { |l| Color::Lab.new([l]) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def ink_limit(method=DeltaEMethod)
|
40
|
+
# ;;@samples.each { |s| warn "%s-%s: %.2f => %.2f" % [@channel, s.id, s.input.value, s.output.value] }
|
41
|
+
@samples.each_with_index do |sample, i|
|
42
|
+
if i > 0
|
43
|
+
previous_sample = @samples[i - 1]
|
44
|
+
return previous_sample if sample.output_value < previous_sample.output_value
|
45
|
+
return previous_sample if previous_sample.output.delta_e(sample.output, method) < DeltaETolerance
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@samples.last
|
49
|
+
end
|
50
|
+
|
51
|
+
def trim_to_limit
|
52
|
+
limit = ink_limit
|
53
|
+
i = @samples.index(limit)
|
54
|
+
;;warn "trimming curve #{@channel} to sample \##{i}: #{limit}"
|
55
|
+
@samples.slice!(i + 1 .. -1)
|
56
|
+
@input_spliner = @output_spliner = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def normalize_inputs
|
60
|
+
scale = 1 / @samples.last.input.value
|
61
|
+
@samples.each do |sample|
|
62
|
+
sample.input = Color::Gray.new(k: sample.input.k * scale)
|
63
|
+
end
|
64
|
+
@input_spliner = @output_spliner = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def verify_increasing_values
|
68
|
+
@samples.each_with_index do |sample, i|
|
69
|
+
if i > 0
|
70
|
+
previous_sample = @samples[i - 1]
|
71
|
+
if sample.output_value < previous_sample.output_value
|
72
|
+
raise "Samples not in increasing order (#{sample.label} [#{i}]: #{sample.output_value} < #{previous_sample.output_value})"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def dmin
|
79
|
+
@samples.first.output_value
|
80
|
+
end
|
81
|
+
|
82
|
+
def dmax
|
83
|
+
@samples.last.output_value
|
84
|
+
end
|
85
|
+
|
86
|
+
def dynamic_range
|
87
|
+
[dmin, dmax]
|
88
|
+
end
|
89
|
+
|
90
|
+
def draw_svg(svg, options={})
|
91
|
+
size = options[:size] || 500
|
92
|
+
|
93
|
+
# draw interpolated curve
|
94
|
+
svg.g(fill: 'none', stroke: 'green', :'stroke-width' => 1) do
|
95
|
+
samples = (0..1).step(1.0 / size).map do |n|
|
96
|
+
[size * n, size * output_for_input(n)]
|
97
|
+
end
|
98
|
+
svg.polyline(points: samples.map { |pt| pt.join(',') }.join(' '))
|
99
|
+
end
|
100
|
+
|
101
|
+
# draw markers for ink limits
|
102
|
+
{
|
103
|
+
density: 'gray',
|
104
|
+
# cie76: 'red',
|
105
|
+
# cie94: 'green',
|
106
|
+
# cmclc: 'blue',
|
107
|
+
}.each do |method, color|
|
108
|
+
limit = ink_limit(method)
|
109
|
+
x, y = size * limit.input_value, size * limit.output_value
|
110
|
+
svg.g(stroke: color, :'stroke-width' => 3) do
|
111
|
+
svg.line(x1: x, y1: y + 8, x2: x, y2: y - 8)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# if (limit = ink_limit)
|
116
|
+
# x, y = size * limit.input_value, size * limit.output_value
|
117
|
+
# svg.g(stroke: 'black', :'stroke-width' => 3) do
|
118
|
+
# svg.line(x1: x, y1: y + 15, x2: x, y2: y - 15)
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
|
122
|
+
# draw individual samples
|
123
|
+
@samples.each_with_index do |sample, i|
|
124
|
+
svg.circle(
|
125
|
+
cx: size * sample.input_value,
|
126
|
+
cy: size * sample.output_value,
|
127
|
+
r: 3,
|
128
|
+
stroke: 'none',
|
129
|
+
fill: "rgb(#{sample.output.to_rgb.to_a.join(',')})",
|
130
|
+
title: sample.label)
|
131
|
+
end
|
132
|
+
|
133
|
+
svg.target!
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|