qttk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +36 -0
  5. data/README.markdown +131 -0
  6. data/Rakefile +17 -0
  7. data/TODO.markdown +236 -0
  8. data/bin/qt +7 -0
  9. data/etc/gutenprint/gp-tool.rb +56 -0
  10. data/etc/gutenprint/gutenprint-filter.c +400 -0
  11. data/etc/gutenprint/gutenprint.rb +86 -0
  12. data/etc/gutenprint/stp-test +326 -0
  13. data/etc/images/3551599565_db282cf840_o.jpg +0 -0
  14. data/etc/images/4843122063_d582c569e9_o.jpg +0 -0
  15. data/etc/images/4843128953_83c1770907_o.jpg +0 -0
  16. data/lib/quadtone.rb +56 -0
  17. data/lib/quadtone/cgats.rb +137 -0
  18. data/lib/quadtone/cluster_calculator.rb +81 -0
  19. data/lib/quadtone/color.rb +83 -0
  20. data/lib/quadtone/color/cmyk.rb +112 -0
  21. data/lib/quadtone/color/device_n.rb +23 -0
  22. data/lib/quadtone/color/gray.rb +46 -0
  23. data/lib/quadtone/color/lab.rb +150 -0
  24. data/lib/quadtone/color/qtr.rb +71 -0
  25. data/lib/quadtone/color/rgb.rb +71 -0
  26. data/lib/quadtone/color/xyz.rb +80 -0
  27. data/lib/quadtone/curve.rb +138 -0
  28. data/lib/quadtone/curve_set.rb +196 -0
  29. data/lib/quadtone/descendants.rb +9 -0
  30. data/lib/quadtone/environment.rb +5 -0
  31. data/lib/quadtone/extensions/math.rb +11 -0
  32. data/lib/quadtone/extensions/pathname3.rb +11 -0
  33. data/lib/quadtone/printer.rb +106 -0
  34. data/lib/quadtone/profile.rb +217 -0
  35. data/lib/quadtone/quad_file.rb +59 -0
  36. data/lib/quadtone/renderer.rb +139 -0
  37. data/lib/quadtone/run.rb +10 -0
  38. data/lib/quadtone/sample.rb +32 -0
  39. data/lib/quadtone/separator.rb +36 -0
  40. data/lib/quadtone/target.rb +277 -0
  41. data/lib/quadtone/tool.rb +61 -0
  42. data/lib/quadtone/tools/add_printer.rb +73 -0
  43. data/lib/quadtone/tools/characterize.rb +43 -0
  44. data/lib/quadtone/tools/chart.rb +31 -0
  45. data/lib/quadtone/tools/check.rb +16 -0
  46. data/lib/quadtone/tools/dir.rb +15 -0
  47. data/lib/quadtone/tools/edit.rb +23 -0
  48. data/lib/quadtone/tools/init.rb +82 -0
  49. data/lib/quadtone/tools/install.rb +15 -0
  50. data/lib/quadtone/tools/linearize.rb +28 -0
  51. data/lib/quadtone/tools/list.rb +19 -0
  52. data/lib/quadtone/tools/print.rb +38 -0
  53. data/lib/quadtone/tools/printer_options.rb +40 -0
  54. data/lib/quadtone/tools/rename.rb +17 -0
  55. data/lib/quadtone/tools/render.rb +43 -0
  56. data/lib/quadtone/tools/rewrite.rb +15 -0
  57. data/lib/quadtone/tools/separate.rb +71 -0
  58. data/lib/quadtone/tools/show.rb +15 -0
  59. data/lib/quadtone/tools/test.rb +26 -0
  60. data/qttk.gemspec +34 -0
  61. 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